SLOPSHOPPER

privacy-shield-th

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

newpaneguardcommandtoaststatus
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · privacy-shield-th
│ ┃ 🛡️ Privacy ✕ › fix the failing auth test and add an audit log call │ ┃ │ ┃ 08:53 ⏺ Read(src/auth.ts) │ ┃ วันพฤหัสบดีที่ 9 ตุลาคม 2568 ⎿ 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 │ │ › /shield │ ⎿ privacy-shield-th: 🛡️ บังจอแล้ว (/shield off เพื่อปิด) │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ privacy-shield-th: 🛡️ โหมดส่วนตัว

Draws

Pane · 🛡️ Privacy
08:53 วันพฤหัสบดีที่ 9 ตุลาคม 2568 🌿 พักหน้าจอสักครู่
README

Mod 42: Privacy Shield TH

โหมดความเป็นส่วนตัวตอนแชร์หน้าจอหรือมีคนเดินผ่าน: เปิดแผงนาฬิกาสงบๆ บังเนื้อหา, ปิดบัง API key / รหัสผ่าน / token ในผลลัพธ์ Bash ก่อนขึ้นจอ (โมเดลก็จะเห็นเป็น •••••••• ด้วย), การ์ดโฟกัสงานเดียว และสรุปงานวันนี้จาก git log ของจริง

คำสั่ง

คำสั่งทำอะไร
/shield [ข้อความ]บังจอด้วยแผงนาฬิกา
/shield offปิดแผง
`/shield mask on\off`ปิดบังข้อมูลลับในผลลัพธ์ Bash
/shield focus <งาน>การ์ดโฟกัสงานเดียว
/shield todaycommit วันนี้จาก git

ติดตั้ง

/plugin install privacy-shield-th --marketplace tksoft11/claude-code-mods-th

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

โค้ดสอนอะไร

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

  1. รูปแบบข้อมูลลับที่ต้องปิดบัง
  2. เปิด/ปิดแผงและนาฬิกาที่เดินเฉพาะตอนแผงเปิด
  3. ดักผลลัพธ์ Bash ใน tool.call แล้วแก้ก่อนแสดง
  4. แผงบังจอและการ์ดโฟกัส
  5. คำสั่ง /shield

หมายเหตุ

ต้นฉบับมี "จอปลอม" (Excel/อีเมล/โค้ดปลอม) ไว้หลอกคนดู ฉบับนี้ตัดออก เน้นความเป็นส่วนตัวและโฟกัสจริง การปิดบังครอบคลุมผลลัพธ์ Bash เท่านั้น ยังไม่รวมไฟล์ที่ Claude อ่านด้วย Read

Source 1 files
hooks/register.tsx 207 lines
1/**
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