SLOPSHOPPER

dev-awake-th

แก้ง่วงสำหรับ Dev: มุกโปรแกรมเมอร์ ควิซ โจทย์โค้ดสั้นๆ Pomodoro และเตือนพัก ดื่มน้ำ พักสายตา ขณะเขียนโค้ดนานๆ

newcommandtoaststatuspromptmodel
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · dev-awake-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 › /awake ⎿ dev-awake-th: ☕ Dev Awake TH (Mod 31) ⎿ dev-awake-th: ======================================== ⎿ dev-awake-th: /awake joke มุกโปรแกรมเมอร์ ⎿ dev-awake-th: /awake quiz | answer <a-d> ควิซความรู้ Dev ⎿ dev-awake-th: /awake challenge โจทย์โค้ดสั้นๆ (ให้ Claude ช่วยตรวจได้) ⎿ dev-awake-th: /awake pomodoro [โฟกัส] [พัก] เริ่ม Pomodoro (นาที, ค่าเริ่มต้น 25 5) | pomodoro stop ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

Mod 31: Dev Awake TH

แก้ง่วงระหว่างเขียนโค้ด: มุกโปรแกรมเมอร์ ควิซความรู้ Dev โจทย์โค้ดสั้นๆ Pomodoro ที่นับถอยหลังใน status line และเตือนพักอัตโนมัติ (ดื่มน้ำ พักสายตา 20-20-20 ยืดเส้น) รวมถึงทักเมื่อทำงานดึกเกินไป

คำสั่ง

คำสั่งทำอะไร
/awake jokeมุกโปรแกรมเมอร์
/awake quiz / answer <a-d>ควิซความรู้ Dev พร้อมนับคะแนน
/awake challengeโจทย์โค้ดสั้นๆ พร้อมคำใบ้
/awake pomodoro [โฟกัส] [พัก]เริ่ม Pomodoro (ค่าเริ่มต้น 25/5 นาที)
/awake pomodoro stopหยุด
/awake remind on [นาที] / offเตือนพักอัตโนมัติ (ค่าเริ่มต้น 50 นาที)
/awake statusสถานะทั้งหมด
/awake quote / quote aiคำคมของคนจริง (อ้างที่มาถูก) / ให้ Claude แต่งคำคมใหม่
/awake quote on [นาที] / quote offคำคมหมุนเวียนในแถบสถานะ (ไม่แย่งที่ Pomodoro)

ติดตั้ง

/plugin install dev-awake-th --marketplace tksoft11/claude-code-mods-th

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

โค้ดสอนอะไร

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

  1. คลังเนื้อหา (มุก ควิซ โจทย์ คำเตือน)
  2. state ของ Mod
  3. Pomodoro ด้วย $.clock.every และ $.ui.status
  4. ทักเมื่อทำงานดึกผ่าน prompt.submit
  5. คำสั่ง /awake

หมายเหตุ

การเตือนพักและ Pomodoro ทำงานเฉพาะตอนเปิด Claude Code อยู่

Source 1 files
hooks/register.tsx 273 lines
1/**
2 * ============================================================================
3 * MOD 31: Dev Awake TH - แก้ง่วงสำหรับ Dev (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: เทคนิค Pomodoro และกฎ 20-20-20 ถนอมสายตา
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. มุกโปรแกรมเมอร์, ควิซความรู้ Dev, โจทย์โค้ดสั้นๆ ให้สมองตื่น
9 * 2. Pomodoro: จับเวลาโฟกัส/พัก แสดงนับถอยหลังใน status line
10 * 3. เตือนพักอัตโนมัติ: ดื่มน้ำ, พักสายตา 20-20-20, ลุกยืดเส้น (หมุนเวียน)
11 * 4. ทักเมื่อทำงานดึกเกินไป
12 * 5. คำคมหมุนเวียนในแถบสถานะ (ของคนจริง อ้างที่มาถูก) หรือให้ Claude แต่งคำคมใหม่
13 */
14
15import type { EngineInterface, Register, Timer } from 'claude-code'
16
17// คำคมที่รู้ที่มาแน่ชัด (หลีกเลี่ยงคำคมที่มักถูกอ้างผิดคน)
18const QUOTES = [
19  'Talk is cheap. Show me the code. — Linus Torvalds',
20  'Premature optimization is the root of all evil. — Donald Knuth',
21  'Simplicity is prerequisite for reliability. — Edsger W. Dijkstra',
22  'Programs must be written for people to read. — Abelson & Sussman',
23  'Make it work, make it right, make it fast. — Kent Beck',
24  'Any fool can write code that a computer can understand. Good programmers write code that humans can understand. — Martin Fowler',
25  'There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton',
26  'Testing shows the presence, not the absence of bugs. — Edsger W. Dijkstra',
27  'The most dangerous phrase in the language is "we\'ve always done it this way". — Grace Hopper',
28  'Weeks of coding can save you hours of planning. — นิรนาม',
29  'ช้าๆ ได้พร้าเล่มงาม — สุภาษิตไทย',
30  'น้ำหยดลงหินทุกวัน หินมันยังกร่อน — สุภาษิตไทย',
31  'ไม่มีใครแก่เกินเรียน — สำนวนไทย',
32  'ทำดีได้ดี ทำโค้ดดีได้รีวิวผ่าน — ดัดแปลงจากสุภาษิต',
33]
34let quoteTimer: Timer | undefined
35
36// ==========================================================================
37// บทที่ 1: คลังเนื้อหา - เพิ่มของทีมคุณเองได้
38// ==========================================================================
39const JOKES = [
40  'Dev: "บนเครื่องผมรันได้นะ"\nหัวหน้า: "งั้นเราส่งเครื่องคุณให้ลูกค้าเลยไหม"',
41  'ทำไมโปรแกรมเมอร์ชอบโหมดมืด?\nเพราะแสงดึงดูดบั๊ก 🐛',
42  'แก้บั๊ก 1 ตัว เกิดใหม่ 3 ตัว\nนี่ไม่ใช่บั๊ก นี่คือ feature การขยายพันธุ์',
43  'Git commit ล่าสุด: "แก้นิดเดียว"\nไฟล์ที่เปลี่ยน: 147',
44  'มี 10 ประเภทของคนบนโลก\nคนที่เข้าใจเลขฐานสอง กับคนที่ไม่เข้าใจ',
45  'เขียนโค้ดเสร็จ 90% แล้ว\nเหลืออีก 90% ที่เหลือ',
46  'ลูกค้า: "ขอแก้นิดเดียวนะ"\nDev: *เปิด Jira เตรียมใจ*',
47  'ตั้งชื่อตัวแปรยากกว่าเขียนโค้ดทั้งฟังก์ชัน\n`data2_final_จริงๆ_v3`',
48  'คอมเมนต์ในโค้ด: "อย่าแตะส่วนนี้"\nมือ: *แตะ*',
49  'Deploy วันศุกร์เย็น = ได้ทำงานวันเสาร์ฟรี',
50  'Stack Overflow ล่ม = วันหยุดราชการของ Dev',
51  'กาแฟคืออะไร? คือตัวแปลงคาเฟอีนเป็นโค้ด ☕→💻',
52]
53
54interface Quiz {
55  q: string
56  choices: string[]
57  answer: number // index ของคำตอบที่ถูก
58  why: string
59}
60const QUIZZES: Quiz[] = [
61  { q: 'HTTP status ไหนหมายถึง "ไม่พบหน้า"?', choices: ['200', '301', '404', '500'], answer: 2, why: '404 Not Found' },
62  { q: 'คำสั่ง git ไหนใช้ดูประวัติ commit?', choices: ['git status', 'git log', 'git diff', 'git show-branch'], answer: 1, why: 'git log แสดงประวัติ commit' },
63  { q: 'ใน JavaScript `typeof null` ได้อะไร?', choices: ['"null"', '"undefined"', '"object"', '"number"'], answer: 2, why: 'บั๊กประวัติศาสตร์ของ JS ที่แก้ไม่ได้แล้ว' },
64  { q: 'Big-O ของการค้นหาแบบ binary search?', choices: ['O(1)', 'O(log n)', 'O(n)', 'O(n log n)'], answer: 1, why: 'แบ่งครึ่งทุกรอบ' },
65  { q: 'SQL คำสั่งไหนใช้ลบข้อมูลบางแถว?', choices: ['DROP', 'TRUNCATE', 'DELETE', 'REMOVE'], answer: 2, why: 'DELETE ... WHERE ลบเฉพาะแถวที่ตรงเงื่อนไข' },
66  { q: 'พอร์ตเริ่มต้นของ HTTPS?', choices: ['80', '443', '8080', '22'], answer: 1, why: 'HTTPS ใช้ 443, HTTP ใช้ 80' },
67  { q: 'CSS ไหนจัดของให้อยู่กลางทั้งแนวตั้งและแนวนอนใน flex?', choices: ['align-items + justify-content: center', 'text-align: center', 'margin: auto 0', 'float: center'], answer: 0, why: 'flex ใช้ align-items (แกนขวาง) และ justify-content (แกนหลัก)' },
68  { q: 'REST: method ไหนควร idempotent?', choices: ['POST', 'PUT', 'PATCH (เสมอ)', 'ไม่มีเลย'], answer: 1, why: 'PUT ส่งซ้ำได้ผลเหมือนเดิม' },
69]
70
71const CHALLENGES = [
72  { title: 'FizzBuzz ภาษาไทย', task: 'พิมพ์ 1-30 ถ้าหาร 3 ลงตัวพิมพ์ "ฟิซ" หาร 5 ลงตัวพิมพ์ "บัซ" หารทั้งคู่พิมพ์ "ฟิซบัซ"', hint: 'เช็คหาร 15 ก่อน' },
73  { title: 'กลับคำ', task: 'เขียนฟังก์ชันกลับลำดับคำในประโยค "สวัสดี ชาว โลก" -> "โลก ชาว สวัสดี"', hint: 'split(" ") แล้ว reverse()' },
74  { title: 'นับสระ', task: 'นับจำนวนสระภาษาอังกฤษ (a e i o u) ในข้อความ', hint: 'ใช้ regex /[aeiou]/gi' },
75  { title: 'Palindrome', task: 'เช็คว่าคำอ่านกลับหลังได้เหมือนเดิมไหม เช่น "level"', hint: 'เทียบกับ s.split("").reverse().join("")' },
76  { title: 'หาเลขซ้ำ', task: 'จาก array [3, 1, 4, 1, 5, 9, 2, 6, 5] หาเลขที่ซ้ำทั้งหมด', hint: 'ใช้ Set เก็บที่เคยเห็น' },
77]
78
79const REMINDERS = [
80  '💧 ดื่มน้ำสักแก้ว สมองขาดน้ำแล้วง่วง',
81  '👀 กฎ 20-20-20: มองไกล 20 ฟุต (6 เมตร) นาน 20 วินาที',
82  '🧘 ลุกยืดเส้น หมุนไหล่ 10 รอบ แล้วค่อยกลับมา',
83  '🌬️ หายใจลึกๆ 5 ครั้ง ช้าๆ',
84  '🚶 เดินไปเติมน้ำหรือเข้าห้องน้ำ ให้ขาได้ขยับ',
85]
86
87// ==========================================================================
88// บทที่ 2: state ของ Mod
89// ==========================================================================
90let quiz: Quiz | undefined
91let score = { right: 0, total: 0 }
92let pomodoro: { phase: 'focus' | 'break'; endsAt: number; focusMin: number; breakMin: number; rounds: number } | undefined
93let pomoTimer: Timer | undefined
94let reminderTimer: Timer | undefined
95let reminderIndex = 0
96let lateWarnedOn = ''
97
98const pick = <T,>(list: T[]): T => list[Math.floor(Math.random() * list.length)] as T
99
100function mmss(ms: number): string {
101  const s = Math.max(0, Math.round(ms / 1000))
102  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`
103}
104
105// ==========================================================================
106// บทที่ 3: Pomodoro - tick ทุก 1 วินาที อัปเดต status line และสลับโฟกัส/พัก
107// ==========================================================================
108function stopPomodoro($: EngineInterface): void {
109  pomoTimer?.cancel()
110  pomoTimer = undefined
111  pomodoro = undefined
112  $.ui.status(undefined)
113}
114
115function startPomodoro($: EngineInterface, now: number, focusMin: number, breakMin: number): void {
116  stopPomodoro($)
117  pomodoro = { phase: 'focus', endsAt: now + focusMin * 60_000, focusMin, breakMin, rounds: 0 }
118  pomoTimer = $.clock.every(1000, () => void tickPomodoro($))
119}
120
121async function tickPomodoro($: EngineInterface): Promise<void> {
122  if (!pomodoro) return
123  const now = await $.clock.now()
124  const left = pomodoro.endsAt - now
125  if (left <= 0) {
126    if (pomodoro.phase === 'focus') {
127      pomodoro.rounds++
128      pomodoro.phase = 'break'
129      pomodoro.endsAt = now + pomodoro.breakMin * 60_000
130      $.ui.toast(`🍅 ครบ ${pomodoro.focusMin} นาทีแล้ว (รอบที่ ${pomodoro.rounds}) พัก ${pomodoro.breakMin} นาที\n${pick(REMINDERS)}`, { timeoutMs: 15_000 })
131    } else {
132      pomodoro.phase = 'focus'
133      pomodoro.endsAt = now + pomodoro.focusMin * 60_000
134      $.ui.toast('🍅 หมดเวลาพัก กลับมาโฟกัสกันต่อ!')
135    }
136  }
137  $.ui.status(`${pomodoro.phase === 'focus' ? '🍅 โฟกัส' : '☕ พัก'} ${mmss(pomodoro.endsAt - now)}`)
138}
139
140const HELP = `☕ Dev Awake TH (Mod 31)
141========================================
142/awake joke               มุกโปรแกรมเมอร์
143/awake quiz | answer <a-d>   ควิซความรู้ Dev
144/awake challenge          โจทย์โค้ดสั้นๆ (ให้ Claude ช่วยตรวจได้)
145/awake pomodoro [โฟกัส] [พัก]   เริ่ม Pomodoro (นาที, ค่าเริ่มต้น 25 5) | pomodoro stop
146/awake remind on [นาที] | off  เตือนพักอัตโนมัติ (ค่าเริ่มต้นทุก 50 นาที)
147/awake quote              คำคมหนึ่งข้อ | quote ai ให้ Claude แต่งใหม่
148/awake quote on [นาที] | off  คำคมหมุนเวียนในแถบสถานะ (ค่าเริ่มต้นทุก 10 นาที)
149/awake status`
150
151export const register: Register = on => {
152  on('session.start', async ($, e, next) => {
153    score = { right: 0, total: 0 }
154    const saved = (await $.store.get('remind')) as { minutes?: number } | undefined
155    if (saved?.minutes) reminderTimer = $.clock.every(saved.minutes * 60_000, () => $.ui.toast(REMINDERS[reminderIndex++ % REMINDERS.length] ?? ''))
156    const q = (await $.store.get('quote')) as { minutes?: number } | undefined
157    if (q?.minutes) quoteTimer = $.clock.every(q.minutes * 60_000, () => {
158      if (!pomodoro) $.ui.status(`💬 ${pick(QUOTES)}`)
159    })
160    await $.command.register({ name: 'awake', description: 'แก้ง่วง: มุก ควิซ โจทย์ Pomodoro เตือนพัก (/awake help)', argumentHint: 'joke|quiz|challenge|pomodoro|remind' })
161    return next(e)
162  })
163
164  // ==========================================================================
165  // บทที่ 4: ทักเมื่อทำงานดึก (หลัง 23:00 หรือก่อนตี 5) วันละครั้ง
166  // ==========================================================================
167  on('prompt.submit', async ($, e, next) => {
168    const d = new Date(await $.clock.now())
169    const today = d.toDateString()
170    if ((d.getHours() >= 23 || d.getHours() < 5) && lateWarnedOn !== today) {
171      lateWarnedOn = today
172      $.ui.toast('🌙 ดึกแล้วนะ โค้ดที่เขียนตอนง่วงมักต้องแก้พรุ่งนี้ ลอง commit แล้วไปนอนไหม')
173    }
174    return next(e)
175  })
176
177  // ==========================================================================
178  // บทที่ 5: คำสั่ง /awake
179  // ==========================================================================
180  on('command.run', { command: 'awake' }, async ($, e) => {
181    const [sub = 'help', a1, a2] = e.args.trim().split(/\s+/).filter(Boolean)
182    const now = await $.clock.now()
183    switch (sub) {
184      case 'joke':
185        return { text: `😂 ${pick(JOKES)}` }
186      case 'quiz': {
187        quiz = pick(QUIZZES)
188        return { text: [`🧠 ${quiz.q}`, ...quiz.choices.map((c, i) => `  ${'abcd'[i]}) ${c}`), '', 'ตอบด้วย /awake answer a'].join('\n') }
189      }
190      case 'answer': {
191        if (!quiz) return { text: 'ยังไม่มีคำถาม /awake quiz' }
192        const i = 'abcd'.indexOf((a1 ?? '').toLowerCase())
193        if (i < 0) return { text: 'ตอบ a, b, c หรือ d' }
194        const correct = i === quiz.answer
195        score = { right: score.right + (correct ? 1 : 0), total: score.total + 1 }
196        const text = `${correct ? '✅ ถูกต้อง!' : `❌ ยังไม่ใช่ คำตอบคือ ${'abcd'[quiz.answer]}) ${quiz.choices[quiz.answer]}`}\n💡 ${quiz.why}\nคะแนน ${score.right}/${score.total}`
197        quiz = undefined
198        return { text }
199      }
200      case 'challenge': {
201        const c = pick(CHALLENGES)
202        return { text: `🧩 ${c.title}\n${c.task}\n\n💡 คำใบ้: ${c.hint}\nเขียนเสร็จแล้วพิมพ์ให้ Claude ตรวจได้เลย เช่น "ตรวจโค้ด FizzBuzz นี้ให้หน่อย"` }
203      }
204      case 'pomodoro': {
205        if (a1 === 'stop') {
206          const rounds = pomodoro?.rounds ?? 0
207          stopPomodoro($)
208          return { text: `⏹️ หยุด Pomodoro (ทำไป ${rounds} รอบ)` }
209        }
210        const focus = Math.min(90, Math.max(1, Number(a1) || 25))
211        const rest = Math.min(30, Math.max(1, Number(a2) || 5))
212        startPomodoro($, now, focus, rest)
213        return { text: `🍅 เริ่มโฟกัส ${focus} นาที แล้วพัก ${rest} นาที นับถอยหลังอยู่ใน status line` }
214      }
215      case 'remind': {
216        reminderTimer?.cancel()
217        reminderTimer = undefined
218        if (a1 === 'off') {
219          await $.store.delete('remind')
220          return { text: '🔕 ปิดการเตือนพักแล้ว' }
221        }
222        const minutes = Math.min(180, Math.max(10, Number(a2) || 50))
223        reminderTimer = $.clock.every(minutes * 60_000, () => $.ui.toast(REMINDERS[reminderIndex++ % REMINDERS.length] ?? ''))
224        await $.store.set('remind', { minutes })
225        return { text: `🔔 จะเตือนพักทุก ${minutes} นาที (ดื่มน้ำ / พักสายตา / ยืดเส้น สลับกัน)` }
226      }
227      case 'quote': {
228        if (a1 === 'off') {
229          quoteTimer?.cancel()
230          quoteTimer = undefined
231          if (!pomodoro) $.ui.status(undefined)
232          await $.store.delete('quote')
233          return { text: 'ปิดคำคมในแถบสถานะแล้ว' }
234        }
235        if (a1 === 'on') {
236          const minutes = Math.max(1, Math.min(120, Number(a2) || 10))
237          quoteTimer?.cancel()
238          // Pomodoro ใช้แถบสถานะอยู่ก็ไม่แย่ง
239          const show = () => {
240            if (!pomodoro) $.ui.status(`💬 ${pick(QUOTES)}`)
241          }
242          show()
243          quoteTimer = $.clock.every(minutes * 60_000, show)
244          await $.store.set('quote', { minutes })
245          return { text: `💬 จะเปลี่ยนคำคมในแถบสถานะทุก ${minutes} นาที` }
246        }
247        if (a1 === 'ai') {
248          const system = 'แต่งคำคมสั้นสำหรับนักพัฒนาซอฟต์แวร์ 1 ประโยค ภาษาไทยที่เป็นธรรมชาติ คมคาย มีอารมณ์ขันเล็กน้อย ไม่เกิน 80 ตัวอักษร ห้ามอ้างว่าเป็นคำพูดของบุคคลจริง ตอบเฉพาะคำคม'
249          for (const model of ['haiku', await $.session.model()]) {
250            const r = await $.model.complete({ model, system, prompt: `หัวข้อสุ่ม: ${pick(['บั๊ก', 'การตั้งชื่อตัวแปร', 'deadline', 'code review', 'กาแฟ', 'เทสต์', 'refactor', 'production'])}`, maxTokens: 120 }).catch(() => undefined)
251            if (r?.isAnswered) return { text: `💬 ${r.text.trim().replace(/^["“]|["”]$/g, '')} — แต่งโดย Claude` }
252          }
253          return { text: `💬 ${pick(QUOTES)}` }
254        }
255        return { text: `💬 ${pick(QUOTES)}` }
256      }
257      case 'status':
258        return { text: `☕ Pomodoro: ${pomodoro ? `${pomodoro.phase === 'focus' ? 'โฟกัส' : 'พัก'} เหลือ ${mmss(pomodoro.endsAt - now)} (รอบ ${pomodoro.rounds})` : 'ปิด'} | เตือนพัก: ${reminderTimer ? 'เปิด' : 'ปิด'} | ควิซ ${score.right}/${score.total}` }
259      default:
260        return { text: HELP }
261    }
262  })
263}
264
265/**
266 * ========================================================================
267 * การบ้าน / ต่อยอด
268 * ========================================================================
269 * 1. ให้ Claude สร้างควิซใหม่จากโค้ดในโปรเจกต์คุณเอง ($.model.complete)
270 * 2. ส่งเตือนพักไปมือถือ/นาฬิกา (ต่อกับ Mod 22)
271 * 3. เก็บสถิติ Pomodoro รายวันใน $.store แล้วสรุปตอนเย็น
272 */
273