เปิดเว็บจาก Claude Code: ให้ Claude สรุปเว็บเป็นไทย, ดูข้อความดิบ, เว็บโปรด, ค้นหา และประวัติ

เปิดเว็บโดยไม่ต้องออกจาก Claude Code ให้ Claude เปิดแล้วสรุปเป็นภาษาไทย หรือดึงข้อความดิบมาดูเองโดยไม่ใช้ token มีเว็บโปรดสาย dev ไทย ค้นหาเว็บ และประวัติ (รวมเว็บที่ Claude เปิดเองด้วย WebFetch)
| คำสั่ง | ทำอะไร |
|---|---|
/browser react.dev | ให้ Claude เปิดและสรุปเป็นไทย |
/browser raw example.com | ดึงข้อความดิบเอง |
/browser fav [ชื่อ] | ดู/เปิดเว็บโปรด |
/browser search <คำ> | ให้ Claude ค้นเว็บแล้วสรุป |
/browser history | ประวัติ |
/plugin install terminal-browser-th --marketplace tksoft11/claude-code-mods-th
ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)
เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:
{ tool: "WebFetch" }$.prompt.submit$.http.fetchnew URL()raw แสดงสูงสุด 3,000 ตัวอักษร และตัด HTML แบบง่าย
hooks/register.tsx 175 lines1/**
2 * ============================================================================
3 * MOD 08: Terminal Browser TH - เปิดเว็บโดยไม่ต้องออกจาก Claude Code (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: https://github.com/zenbu-labs/terminal-browser
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. /browser <url> ให้ Claude เปิดเว็บ (WebFetch) แล้วสรุปเป็นภาษาไทย
9 * 2. /browser raw <url> ดึงหน้าเว็บเองด้วย $.http.fetch แล้วแสดงข้อความดิบ (ไม่ใช้ token)
10 * 3. /browser fav [ชื่อ] เว็บโปรดคนไทยสาย dev
11 * 4. /browser search <คำ> ให้ Claude ค้นเว็บแล้วสรุป
12 * 5. /browser history ประวัติเว็บที่เปิด (รวมที่ Claude เปิดเองด้วย WebFetch)
13 */
14
15import type { EngineInterface, Register } from 'claude-code'
16
17interface HistoryItem {
18 url: string
19 time: number
20 status: 'loading' | 'success' | 'error'
21}
22
23// ==========================================================================
24// บทที่ 1: เก็บประวัติ (เก็บแค่ 20 รายการล่าสุด)
25// ==========================================================================
26let history: HistoryItem[] = []
27
28function addToHistory(url: string, time: number): HistoryItem {
29 const item: HistoryItem = { url, time, status: 'loading' }
30 history.push(item)
31 if (history.length > 20) history.shift()
32 return item
33}
34
35// ==========================================================================
36// บทที่ 2: เว็บโปรดคนไทย - แก้/เพิ่มได้ตามใจ
37// ==========================================================================
38const THAI_FAVORITES = [
39 { name: 'Google TH', url: 'https://www.google.co.th' },
40 { name: 'Pantip', url: 'https://pantip.com' },
41 { name: 'GitHub', url: 'https://github.com' },
42 { name: 'MDN', url: 'https://developer.mozilla.org/th/' },
43 { name: 'NPM', url: 'https://www.npmjs.com' },
44 { name: 'React Docs', url: 'https://react.dev' },
45 { name: 'Claude Code Docs', url: 'https://code.claude.com/docs' },
46]
47
48function timeAgo(ms: number): string {
49 const sec = Math.floor(ms / 1000)
50 return sec < 60 ? `${sec}วิที่แล้ว` : sec < 3600 ? `${Math.floor(sec / 60)}นาทีที่แล้ว` : `${Math.floor(sec / 3600)}ชม.ที่แล้ว`
51}
52
53// รับ url แบบไม่มี https:// ก็ได้ เช่น react.dev
54function normalizeUrl(input: string): string | undefined {
55 const withScheme = /^https?:\/\//i.test(input) ? input : `https://${input}`
56 try {
57 return new URL(withScheme).href
58 } catch {
59 return undefined
60 }
61}
62
63// ==========================================================================
64// บทที่ 3: ให้ Claude ทำงานแทน - ส่ง prompt ใหม่ด้วย $.prompt.submit
65// ==========================================================================
66// prompt จะเริ่ม turn ใหม่เมื่อ session ว่าง จึงใช้ void (ไม่ต้องรอ)
67function askClaudeToOpen($: EngineInterface, url: string): void {
68 $.clock.after(50, () => void $.prompt.submit({ text: `ใช้ WebFetch เปิด ${url} แล้วสรุปเนื้อหาหลักเป็นภาษาไทยแบบกระชับ` }).catch(() => undefined)) // ส่งหลังคำสั่งตอบกลับแล้ว (ส่งระหว่าง command.run ไม่ได้)
69}
70
71// ==========================================================================
72// บทที่ 4: ดึงเว็บเองด้วย $.http.fetch แล้วตัด HTML ออกให้อ่านง่าย
73// ==========================================================================
74function htmlToText(html: string): string {
75 return html
76 .replace(/<(script|style|noscript)[\s\S]*?<\/\1>/gi, '')
77 .replace(/<br\s*\/?>|<\/(p|div|h\d|li|tr)>/gi, '\n')
78 .replace(/<[^>]+>/g, '')
79 .replace(/ /g, ' ')
80 .replace(/&/g, '&')
81 .replace(/</g, '<')
82 .replace(/>/g, '>')
83 .replace(/[ \t]+/g, ' ')
84 .replace(/\n\s*\n+/g, '\n')
85 .trim()
86}
87
88export const register: Register = on => {
89 on('session.start', async ($, e, next) => {
90 history = []
91 await $.command.register({
92 name: 'browser',
93 description: 'เปิดเว็บ: /browser <url> | raw <url> | fav [ชื่อ] | search <คำ> | history',
94 argumentHint: '<url>|raw|fav|search|history',
95 })
96 return next(e)
97 })
98
99 // ==========================================================================
100 // บทที่ 5: ดัก WebFetch ของ Claude เพื่อบันทึกประวัติ
101 // ==========================================================================
102 on('tool.call', { tool: 'WebFetch' }, async ($, e, next) => {
103 const item = addToHistory(e.url, await $.clock.now())
104 const ran = await next(e)
105 item.status = ran.deny === undefined && !ran.isError ? 'success' : 'error'
106 return ran
107 })
108
109 on('command.run', { command: 'browser' }, async ($, e) => {
110 const args = e.args.trim()
111 const [sub = '', ...restWords] = args.split(/\s+/)
112 const rest = restWords.join(' ')
113 const now = await $.clock.now()
114
115 if (sub === '') {
116 return { text: 'วิธีใช้: /browser react.dev | /browser raw example.com | /browser fav github | /browser search วิธีใช้ Claude Code | /browser history' }
117 }
118
119 if (sub === 'history') {
120 if (history.length === 0) return { text: '🌐 ยังไม่มีประวัติ' }
121 const icon = { loading: '⏳', success: '✅', error: '❌' }
122 const rows = [...history].reverse().map(h => `${icon[h.status]} ${h.url} (${timeAgo(now - h.time)})`)
123 return { text: ['🌐 ประวัติการเปิดเว็บ', '----------------------------------------', ...rows].join('\n') }
124 }
125
126 if (sub === 'fav') {
127 if (!rest) {
128 const rows = THAI_FAVORITES.map(f => `⭐ ${f.name.padEnd(17)} ${f.url}`)
129 return { text: ['⭐ เว็บโปรด (/browser fav <ชื่อ> เพื่อเปิด)', ...rows].join('\n') }
130 }
131 const found = THAI_FAVORITES.find(f => f.name.toLowerCase().includes(rest.toLowerCase()))
132 if (!found) return { text: `❌ ไม่เจอเว็บโปรดชื่อ "${rest}"` }
133 askClaudeToOpen($, found.url)
134 return { text: `🌐 ให้ Claude เปิด ${found.name} (${found.url})` }
135 }
136
137 if (sub === 'search') {
138 if (!rest) return { text: 'วิธีใช้: /browser search วิธีใช้ Claude Code' }
139 $.clock.after(50, () => void $.prompt.submit({ text: `ค้นเว็บเรื่อง "${rest}" แล้วสรุปผลเป็นภาษาไทย พร้อมลิงก์แหล่งที่มา` }).catch(() => undefined)) // ส่งหลังคำสั่งตอบกลับแล้ว (ส่งระหว่าง command.run ไม่ได้)
140 return { text: `🔎 ให้ Claude ค้นหา: ${rest}` }
141 }
142
143 if (sub === 'raw') {
144 const url = normalizeUrl(rest)
145 if (!url) return { text: 'วิธีใช้: /browser raw https://example.com' }
146 const item = addToHistory(url, now)
147 try {
148 const res = await $.http.fetch(url, { headers: { 'User-Agent': 'claude-code-terminal-browser-th' } })
149 item.status = res.ok ? 'success' : 'error'
150 const body = htmlToText(res.text)
151 const cut = body.length > 3000 ? body.slice(0, 3000) + `\n... (ตัดเหลือ 3,000 จาก ${body.length.toLocaleString('en-US')} ตัวอักษร)` : body
152 return { text: `🌐 ${url} [HTTP ${res.status}]\n----------------------------------------\n${cut}` }
153 } catch (err) {
154 item.status = 'error'
155 return { text: `❌ เปิด ${url} ไม่ได้: ${String(err)}` }
156 }
157 }
158
159 // ไม่ตรง subcommand ไหน = ถือว่าเป็น url
160 const url = normalizeUrl(args)
161 if (!url) return { text: `❌ "${args}" ไม่ใช่ URL ที่ถูกต้อง` }
162 askClaudeToOpen($, url)
163 return { text: `🌐 ให้ Claude เปิด ${url} และสรุปเป็นภาษาไทย` }
164 })
165}
166
167/**
168 * ========================================================================
169 * การบ้าน / ต่อยอด
170 * ========================================================================
171 * 1. ทำแผง (Pane) แสดงประวัติ มีปุ่มกดเปิดซ้ำ (ดูตัวอย่าง Pane ใน Mod 04)
172 * 2. เก็บเว็บโปรดใน $.store ให้ผู้ใช้เพิ่มเองได้ด้วย /browser fav add <ชื่อ> <url>
173 * 3. ใช้ $.model.complete แปลข้อความดิบจาก raw เป็นภาษาไทยโดยไม่เข้าบทสนทนาหลัก
174 */
175