โหมดความเป็นส่วนตัวตอนแชร์จอหรือมีคนเดินผ่าน: กดแล้วแผงสงบๆ บังจอ ซ่อนข้อมูลลับในแถบสถานะ และโหมดโฟกัส

โหมดความเป็นส่วนตัวตอนแชร์หน้าจอหรือมีคนเดินผ่าน: เปิดแผงนาฬิกาสงบๆ บังเนื้อหา, ปิดบัง API key / รหัสผ่าน / token ในผลลัพธ์ Bash ก่อนขึ้นจอ (โมเดลก็จะเห็นเป็น •••••••• ด้วย), การ์ดโฟกัสงานเดียว และสรุปงานวันนี้จาก git log ของจริง
| คำสั่ง | ทำอะไร | |
|---|---|---|
/shield [ข้อความ] | บังจอด้วยแผงนาฬิกา | |
/shield off | ปิดแผง | |
| `/shield mask on\ | off` | ปิดบังข้อมูลลับในผลลัพธ์ Bash |
/shield focus <งาน> | การ์ดโฟกัสงานเดียว | |
/shield today | commit วันนี้จาก git |
/plugin install privacy-shield-th --marketplace tksoft11/claude-code-mods-th
ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)
เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:
ต้นฉบับมี "จอปลอม" (Excel/อีเมล/โค้ดปลอม) ไว้หลอกคนดู ฉบับนี้ตัดออก เน้นความเป็นส่วนตัวและโฟกัสจริง การปิดบังครอบคลุมผลลัพธ์ Bash เท่านั้น ยังไม่รวมไฟล์ที่ Claude อ่านด้วย Read
hooks/register.tsx 207 lines1/**
2 * ============================================================================
3 * MOD 42: Privacy Shield TH - บังจอตอนแชร์หน้าจอ + ปิดบังข้อมูลลับ (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: ปุ่ม "ซ่อนจอ" และโหมดนำเสนอของโปรแกรมประชุมออนไลน์
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. /shield - เปิดแผงสงบๆ (นาฬิกา + ข้อความ) บังเนื้อหาตอนแชร์จอหรือมีคนเดินผ่าน
9 * 2. /shield mask on - ปิดบัง API key / รหัสผ่าน / token ในผลลัพธ์ Bash ก่อนขึ้นจอ
10 * 3. /shield focus <งาน> - การ์ดโฟกัสงานเดียว พร้อมเวลาที่ใช้ไป
11 * 4. /shield today - สรุปงานวันนี้จาก git log ของจริง (ไว้ตอบคำถาม "วันนี้ทำอะไรไป")
12 *
13 * หมายเหตุ: ต้นฉบับมี "จอปลอม Excel/อีเมล" ไว้หลอกคนดู
14 * ฉบับนี้ตัดออก เน้นความเป็นส่วนตัวและโฟกัสจริงๆ ไม่สร้างข้อมูลปลอม
15 */
16
17import type { EngineInterface, Register, Timer } from 'claude-code'
18
19const PANE = 'privacy-shield-th'
20let mode: 'cover' | 'focus' = 'cover'
21let message = 'พักหน้าจอสักครู่'
22let focusTask = ''
23let focusStart = 0
24let masking = false
25let ticker: Timer | undefined
26
27// ==========================================================================
28// บทที่ 1: รูปแบบข้อมูลลับที่ต้องปิดบัง (ชุดเดียวกับ Mod 15 Cyber Guard)
29// ==========================================================================
30const SECRETS: RegExp[] = [
31 /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, // AWS
32 /\bsk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic
33 /\bsk-(?:proj-)?[A-Za-z0-9_-]{32,}/g, // OpenAI
34 /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g, // GitHub token
35 /\b(?:sk|rk)_live_[A-Za-z0-9]{20,}\b/g, // Stripe
36 /\bxox[abpr]-[A-Za-z0-9-]{10,}/g, // Slack
37 /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, // JWT
38 /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
39]
40// KEY=value / password: value ในไฟล์ .env หรือ config
41const ASSIGN = /\b([A-Z0-9_]*(?:SECRET|PASSWORD|PASSWD|TOKEN|API_KEY|APIKEY|PRIVATE_KEY)[A-Z0-9_]*)(\s*[=:]\s*)(["']?)([^\s"']{4,})\3/gi
42
43function mask(text: string): { text: string; count: number } {
44 let count = 0
45 let out = text
46 for (const re of SECRETS)
47 out = out.replace(re, m => {
48 count++
49 return `${m.slice(0, 4)}${'•'.repeat(8)}`
50 })
51 out = out.replace(ASSIGN, (_m, key: string, sep: string, q: string) => {
52 count++
53 return `${key}${sep}${q}${'•'.repeat(8)}${q}`
54 })
55 return { text: out, count }
56}
57
58function clockText(ms: number): { time: string; date: string } {
59 const d = new Date(ms)
60 const days = ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัสบดี', 'ศุกร์', 'เสาร์']
61 const months = ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม']
62 return {
63 time: `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`,
64 date: `วัน${days[d.getDay()]}ที่ ${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear() + 543}`,
65 }
66}
67
68function minutes(ms: number): string {
69 const m = Math.floor(ms / 60_000)
70 return m >= 60 ? `${Math.floor(m / 60)} ชม. ${m % 60} นาที` : `${m} นาที`
71}
72
73// ==========================================================================
74// บทที่ 2: เปิด/ปิดแผง - นาฬิกาเดินเฉพาะตอนแผงเปิดอยู่
75// ==========================================================================
76async function openShield($: EngineInterface): Promise<void> {
77 await $.ui.open({ id: PANE, title: mode === 'focus' ? '🎯 Focus' : '🛡️ Privacy' })
78 ticker ??= $.clock.every(15_000, () => $.ui.invalidate('ui.render'))
79 $.ui.status(mode === 'focus' ? `🎯 ${focusTask}` : '🛡️ โหมดส่วนตัว')
80 $.ui.invalidate('ui.render')
81}
82
83async function closeShield($: EngineInterface): Promise<void> {
84 ticker?.cancel()
85 ticker = undefined
86 $.ui.status(masking ? '🎭 ปิดบังข้อมูลลับ' : undefined)
87 await $.ui.close({ id: PANE })
88}
89
90const HELP = `🛡️ Privacy Shield TH (Mod 42)
91========================================
92/shield [ข้อความ] บังจอด้วยแผงนาฬิกาสงบๆ (กด Esc หรือ /shield off เพื่อปิด)
93/shield off ปิดแผง
94/shield mask on|off ปิดบัง key/รหัสผ่าน/token ในผลลัพธ์ Bash (ตอนนี้: MASK)
95/shield focus <งาน> การ์ดโฟกัสงานเดียว
96/shield today สรุป commit วันนี้จาก git (ของจริง)`
97
98export const register: Register = on => {
99 on('session.start', async ($, e, next) => {
100 masking = ((await $.store.get('masking')) as boolean | undefined) ?? false
101 if (masking) $.ui.status('🎭 ปิดบังข้อมูลลับ')
102 await $.command.register({ name: 'shield', description: 'บังจอตอนแชร์หน้าจอ ปิดบังข้อมูลลับ และโหมดโฟกัส (/shield help)', argumentHint: '[ข้อความ]|off|mask on|focus <งาน>|today' })
103 return next(e)
104 })
105
106 // ==========================================================================
107 // บทที่ 3: ดักผลลัพธ์ Bash แล้วปิดบังข้อมูลลับก่อนแสดงบนจอ (และก่อนโมเดลอ่าน)
108 // ==========================================================================
109 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
110 const r = await next(e)
111 if (!masking || r.deny !== undefined || r.isError) return r
112 const out = mask(r.result.stdout)
113 const err = mask(r.result.stderr)
114 if (out.count + err.count === 0) return r
115 $.ui.toast(`🎭 ปิดบังข้อมูลลับ ${out.count + err.count} จุด`)
116 return { result: { ...r.result, stdout: out.text, stderr: err.text } }
117 })
118
119 // ==========================================================================
120 // บทที่ 4: แผงบังจอ - ตัวใหญ่ อ่านง่าย ไม่มีข้อมูลงาน
121 // ==========================================================================
122 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
123 const { Box, Text } = $.ui.resolve(e)
124 const now = await $.clock.now()
125 const { time, date } = clockText(now)
126 if (mode === 'focus') {
127 return (
128 <Box flexDirection="column" alignItems="center" paddingY={1}>
129 <Text dimColor>งานเดียวที่สำคัญตอนนี้</Text>
130 <Text bold color="#26a69a">
131 🎯 {focusTask}
132 </Text>
133 <Text dimColor>
134 โฟกัสมาแล้ว {minutes(now - focusStart)} · {time}
135 </Text>
136 </Box>
137 )
138 }
139 return (
140 <Box flexDirection="column" alignItems="center" paddingY={1}>
141 <Text bold color="#7e9cd8">
142 {time}
143 </Text>
144 <Text dimColor>{date}</Text>
145 <Text color="#98bb6c">🌿 {message}</Text>
146 </Box>
147 )
148 })
149
150 on('ui.close', async ($, e, next) => {
151 if (e.id === PANE) {
152 ticker?.cancel()
153 ticker = undefined
154 $.ui.status(masking ? '🎭 ปิดบังข้อมูลลับ' : undefined)
155 }
156 return next(e)
157 })
158
159 on('command.run', { command: 'shield' }, async ($, e) => {
160 const [sub = '', ...rest] = e.args.trim().split(/\s+/).filter(Boolean)
161 switch (sub) {
162 case 'help':
163 return { text: HELP.replace('MASK', masking ? 'เปิด' : 'ปิด') }
164 case 'off':
165 await closeShield($)
166 return { text: '👋 ปิดแผงบังจอแล้ว' }
167 case 'mask': {
168 if (rest[0] !== 'on' && rest[0] !== 'off') return { text: `🎭 ปิดบังข้อมูลลับ: ${masking ? 'เปิด' : 'ปิด'} (/shield mask on|off)` }
169 masking = rest[0] === 'on'
170 await $.store.set('masking', masking)
171 $.ui.status(masking ? '🎭 ปิดบังข้อมูลลับ' : undefined)
172 return { text: masking ? '🎭 เปิดการปิดบังแล้ว: key/รหัสผ่าน/token ในผลลัพธ์ Bash จะเป็น ••••••••' : 'ปิดการปิดบังแล้ว' }
173 }
174 case 'focus': {
175 if (rest.length === 0) return { text: 'วิธีใช้: /shield focus เขียนรายงานลูกค้า' }
176 mode = 'focus'
177 focusTask = rest.join(' ').slice(0, 60)
178 focusStart = await $.clock.now()
179 await openShield($)
180 return { text: `🎯 โฟกัส: ${focusTask}` }
181 }
182 case 'today': {
183 // ดึง commit ของวันนี้จาก git จริง (ถ้าโฟลเดอร์นี้ไม่ใช่ repo ก็บอกตรงๆ)
184 const run = await $.process.run(['git', 'log', '--since=midnight', '--pretty=format:%h %s'], { timeoutMs: 10_000 }).catch(() => undefined)
185 if (!run || run.exitCode !== 0) return { text: '❌ โฟลเดอร์นี้ไม่ใช่ git repo หรือไม่มี git' }
186 const lines = run.stdout.split('\n').filter(Boolean)
187 if (lines.length === 0) return { text: '📋 วันนี้ยังไม่มี commit ในโปรเจกต์นี้' }
188 return { text: [`📋 วันนี้ทำไป ${lines.length} commit`, ...lines.slice(0, 20).map(l => ` • ${l}`)].join('\n') }
189 }
190 default:
191 mode = 'cover'
192 if (e.args.trim()) message = e.args.trim().slice(0, 60)
193 await openShield($)
194 return { text: '🛡️ บังจอแล้ว (/shield off เพื่อปิด)' }
195 }
196 })
197}
198
199/**
200 * ========================================================================
201 * การบ้าน / ต่อยอด
202 * ========================================================================
203 * 1. ปิดบังข้อมูลลับในผลลัพธ์ Read ด้วย (ไฟล์ .env)
204 * 2. เปิดโหมดส่วนตัวอัตโนมัติเมื่อเปิดโปรแกรมประชุม (เช็ค process ด้วย $.process.run)
205 * 3. ซ่อนชื่อโฟลเดอร์/ชื่อลูกค้าในแถบสถานะตอนนำเสนอ
206 */
207