เตือนทิปเด็ดภาษาไทยตามสิ่งที่คุณกำลังทำ เช่น ใช้ @ mention ไฟล์, ใช้ Plan Mode, context ใกล้เต็ม

เด้งทิปภาษาไทยตามสิ่งที่คุณกำลังทำ เช่น พิมพ์ path ยาวๆ จะแนะนำให้ใช้ @, ใช้ Write ทับไฟล์เดิมจะแนะนำ Edit, tool พลาดสองครั้งติดจะแนะนำ Plan Mode, context เกิน 80% จะแนะนำ /compact แต่ละทิปแสดงครั้งเดียวต่อ session
| คำสั่ง | ทำอะไร |
|---|---|
/tips | ดูคลังทิปและสถานะ |
/tips random | สุ่มทิป |
/tips reset | ให้แสดงทิปใหม่ทั้งหมด |
/tips <id> | ดูทิปนั้น เช่น /tips plan-mode |
/plugin install you-should-know-th --marketplace tksoft11/claude-code-mods-th
ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)
เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:
prompt.submite.tool === "Write" แล้ว TypeScript รู้ field ให้เองnext(e) (isError)$.session.usage()เพิ่มทิปของทีมเองได้ที่ TIPS ด้านบนของไฟล์
hooks/register.tsx 223 lines1/**
2 * ============================================================================
3 * MOD 03: You Should Know TH - ตัวเตือนทิปเด็ด (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: ปลั๊กอิน "you-should-know" ที่ Anthropic ทำเป็นตัวอย่าง
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. เตือนทิปตามบริบท (contextual tips) ด้วย toast มุมจอ
9 * 2. ไม่บล็อกอะไรเลย แค่ "สอน" ผู้ใช้ระหว่างทำงาน (Educational Mod)
10 * 3. จำว่าแสดงทิปไหนไปแล้วด้วย Set จะได้ไม่สแปม
11 * 4. มีคำสั่ง /tips สำหรับดู สุ่ม และรีเซ็ตทิป
12 */
13
14import type { EngineInterface, Register } from 'claude-code'
15
16interface Tip {
17 id: string
18 trigger: string // จะแสดงเมื่อไหร่ (ไว้อธิบายใน /tips)
19 title: string
20 message: string
21 learnMore: string // คำสั่งหรือสิ่งที่ให้ลอง
22}
23
24// ==========================================================================
25// บทที่ 1: คลังทิป - เพิ่มทิปของทีมคุณเองได้ตรงนี้
26// ==========================================================================
27const TIPS: Tip[] = [
28 {
29 id: 'mention-file',
30 trigger: 'พิมพ์ path ของไฟล์ยาวๆ ใน prompt',
31 title: '💡 ใช้ @ เพื่อ mention ไฟล์ได้',
32 message: "แทนที่จะพิมพ์ path เต็มๆ ให้พิมพ์ '@Button.tsx' แล้วเลือกไฟล์ Claude จะเห็นไฟล์นั้นทันที",
33 learnMore: 'ลองพิมพ์ @ แล้วเลือกไฟล์',
34 },
35 {
36 id: 'clear-session',
37 trigger: 'เรียก tool เกิน 30 ครั้ง',
38 title: '💡 Session ยาวแล้ว ลองสรุปบริบท',
39 message: 'บทสนทนายาวทำให้แต่ละรอบใช้ token มากขึ้น ถ้าเปลี่ยนเรื่องให้ /clear ถ้ายังทำเรื่องเดิมให้ /compact',
40 learnMore: '/compact หรือ /clear',
41 },
42 {
43 id: 'plan-mode',
44 trigger: 'tool ล้มเหลวติดกัน 2 ครั้ง',
45 title: '💡 ลองใช้ Plan Mode ไหม?',
46 message: 'งานที่ซับซ้อนควรให้ Claude วางแผนก่อนลงมือ กด Shift+Tab จนขึ้น plan mode แล้ว Claude จะเสนอแผนให้ดูก่อน',
47 learnMore: 'กด Shift+Tab',
48 },
49 {
50 id: 'edit-vs-write',
51 trigger: 'ใช้ Write ทับไฟล์ที่มีอยู่แล้ว',
52 title: '💡 Edit ปลอดภัยกว่า Write',
53 message: 'Edit แก้เฉพาะจุด ส่วน Write เขียนทับทั้งไฟล์ Write จึงเหมาะกับไฟล์ใหม่มากกว่า',
54 learnMore: "บอก Claude ว่า 'แก้เฉพาะจุดด้วย Edit นะ'",
55 },
56 {
57 id: 'bash-check',
58 trigger: 'รันคำสั่ง Bash ที่อาจอันตราย (rm -rf, sudo, --force)',
59 title: '⚠️ ระวังคำสั่ง Bash นะครับ',
60 message: 'คำสั่งนี้อาจลบหรือเขียนทับของจริง ถ้ามี --dry-run ให้ลองก่อน และอ่านคำสั่งก่อนกดอนุญาต',
61 learnMore: 'เช่น git clean -n ก่อน git clean -f',
62 },
63 {
64 id: 'thai-comment',
65 trigger: 'สุ่มระหว่างทำงาน',
66 title: '🇹🇭 ขอคอมเมนต์ภาษาไทยได้',
67 message: "บอก Claude ว่า 'ใส่คอมเมนต์ภาษาไทยด้วย' ได้เลย เหมาะกับทีมที่ส่งงานให้คนไทยอ่าน",
68 learnMore: "ลอง: 'อธิบายฟังก์ชันนี้เป็นภาษาไทย'",
69 },
70 {
71 id: 'model-choice',
72 trigger: 'สุ่มระหว่างทำงาน',
73 title: '💰 เลือก model ให้เหมาะกับงาน',
74 message: 'งานเล็กๆ เช่น แปลภาษาหรือสรุปไฟล์สั้นๆ ใช้ model ที่เล็กกว่าได้ ถูกและเร็วกว่า',
75 learnMore: '/model',
76 },
77 {
78 id: 'context-usage',
79 trigger: 'context ใช้ไปเกิน 80%',
80 title: '🧠 Context ใกล้เต็มแล้ว!',
81 message: 'ควร /compact ตอนนี้ ก่อนที่ระบบจะสรุปให้เองกลางงาน',
82 learnMore: '/compact หรือ /context เพื่อดูรายละเอียด',
83 },
84 {
85 id: 'first-time',
86 trigger: 'ใช้ Mod นี้ครั้งแรก',
87 title: '👋 ยินดีต้อนรับ! 3 ทิปเริ่มต้น',
88 message: '1. พิมพ์ @ เพื่อเลือกไฟล์ 2. พิมพ์ / เพื่อดูคำสั่ง 3. กด Esc เพื่อหยุด Claude',
89 learnMore: '/tips เพื่อดูทิปทั้งหมด',
90 },
91]
92
93// ==========================================================================
94// บทที่ 2: state ของ Mod - ตัวแปรนอก register() จำค่าข้าม event ได้
95// ==========================================================================
96// (ตัวแปรพวกนี้จะเริ่มใหม่เมื่อ mod ถูก reload ซึ่งสำหรับทิปถือว่าโอเค)
97const shownTips = new Set<string>() // แสดงไปแล้ว -> ไม่แสดงซ้ำ
98let toolCallCount = 0
99let failStreak = 0 // ล้มเหลวติดกันกี่ครั้ง
100
101// แสดงทิปด้วย toast (ถ้ายังไม่เคยแสดง) แล้วจดไว้ใน Set
102function showTip($: EngineInterface, id: string): void {
103 const tip = TIPS.find(t => t.id === id)
104 if (!tip || shownTips.has(id)) return
105
106 $.ui.toast(`${tip.title}\n${tip.message}\n👉 ${tip.learnMore}`, { timeoutMs: 10_000 })
107 shownTips.add(id)
108}
109
110function formatTip(tip: Tip): string {
111 return `${tip.title}\nแสดงเมื่อ: ${tip.trigger}\n${tip.message}\n👉 ${tip.learnMore}`
112}
113
114export const register: Register = on => {
115 on('session.start', async ($, e, next) => {
116 toolCallCount = 0
117 failStreak = 0
118 shownTips.clear()
119
120 await $.command.register({ name: 'tips', description: 'ดูทิปทั้งหมด (/tips random, /tips reset, /tips <id>)', argumentHint: '[list|random|reset|id]' })
121
122 // ทิปต้อนรับแสดงแค่ครั้งแรกที่ติดตั้ง: ใช้ $.store จำข้าม session
123 if ((await $.store.get('welcomed')) !== true) {
124 showTip($, 'first-time')
125 await $.store.set('welcomed', true)
126 }
127 return next(e)
128 })
129
130 // ==========================================================================
131 // บทที่ 3: prompt.submit - ดูว่าผู้ใช้พิมพ์อะไร (แค่อ่าน ไม่แก้)
132 // ==========================================================================
133 on('prompt.submit', ($, e, next) => {
134 // มี path ยาวๆ เช่น src/components/Button.tsx แต่ไม่ได้ใช้ @
135 if (/[\w.-]+\/[\w./-]+\.\w{1,5}\b/.test(e.text) && !e.text.includes('@')) {
136 showTip($, 'mention-file')
137 }
138 return next(e)
139 })
140
141 // ==========================================================================
142 // บทที่ 4: tool.call - เช็ค pattern ก่อนรัน + ดูผลหลังรัน
143 // ==========================================================================
144 on('tool.call', async ($, e, next) => {
145 toolCallCount++
146
147 // เช็คแบบ e.tool === "..." แล้ว TypeScript จะรู้ว่า e มี field อะไร
148 if (e.tool === 'Write' && (await $.fs.exists(e.file_path))) {
149 showTip($, 'edit-vs-write')
150 }
151 if (e.tool === 'Bash' && /rm -rf|sudo |--force/.test(e.command)) {
152 showTip($, 'bash-check')
153 }
154 if (toolCallCount === 31) {
155 showTip($, 'clear-session')
156 } else if (toolCallCount % 15 === 0) {
157 // ทุก 15 ครั้ง สุ่มทิปที่ยังไม่เคยเห็น
158 const notShown = TIPS.filter(t => !shownTips.has(t.id))
159 const pick = notShown[Math.floor(Math.random() * notShown.length)]
160 if (pick) showTip($, pick.id)
161 }
162
163 const ran = await next(e) // ให้ tool รันจริง
164
165 // ผลออกมาเป็น error -> นับ streak, ถ้าพลาด 2 ครั้งติดแนะนำ Plan Mode
166 failStreak = ran.isError ? failStreak + 1 : 0
167 if (failStreak >= 2) showTip($, 'plan-mode')
168
169 return ran
170 })
171
172 // ==========================================================================
173 // บทที่ 5: turn.complete - เช็ค context หลังจบแต่ละรอบ
174 // ==========================================================================
175 on('turn.complete', async ($, e, next) => {
176 const { context } = await $.session.usage()
177 if ((context.percent ?? 0) > 80) showTip($, 'context-usage')
178 return next(e)
179 })
180
181 // ==========================================================================
182 // บทที่ 6: คำสั่งแบบมี subcommand - /tips random, /tips reset
183 // ==========================================================================
184 on('command.run', { command: 'tips' }, async ($, e) => {
185 const arg = e.args.trim()
186
187 if (arg === '' || arg === 'list') {
188 const rows = TIPS.map(t => `${shownTips.has(t.id) ? '✅' : '⬜'} ${t.id.padEnd(16)} ${t.title}`)
189 return {
190 text: [
191 `📚 คลังทิป (Mod 03) - แสดงไปแล้ว ${shownTips.size}/${TIPS.length}`,
192 '----------------------------------------',
193 ...rows,
194 '',
195 '/tips random = สุ่มทิป | /tips reset = รีเซ็ต | /tips <id> = ดูทิปนั้น',
196 ].join('\n'),
197 }
198 }
199 if (arg === 'random') {
200 const tip = TIPS[Math.floor(Math.random() * TIPS.length)]
201 return { text: tip ? formatTip(tip) : 'ยังไม่มีทิป' }
202 }
203 if (arg === 'reset') {
204 shownTips.clear()
205 await $.store.delete('welcomed')
206 return { text: '🔄 รีเซ็ตแล้ว ทิปทั้งหมดจะแสดงใหม่' }
207 }
208
209 const found = TIPS.find(t => t.id === arg)
210 return { text: found ? formatTip(found) : `❌ ไม่เจอทิป "${arg}" ลอง /tips list` }
211 })
212}
213
214/**
215 * ========================================================================
216 * การบ้าน / ต่อยอด
217 * ========================================================================
218 * 1. เพิ่มทิปกฎของบริษัท เช่น "ห้าม push เข้า main ตรงๆ" แล้วเช็คใน tool.call
219 * ว่า e.tool === "Bash" && e.command.includes("git push origin main")
220 * 2. เก็บ shownTips ลง $.store เพื่อจำข้าม session
221 * 3. ใช้ $.model.complete สร้างทิปใหม่จากสิ่งที่ผู้ใช้ทำบ่อย
222 */
223