ดูสถานะ Google Cloud ผ่าน gcloud CLI: บัญชี โปรเจกต์ VM Cloud Run และ billing พร้อม template คำสั่งที่ใช้บ่อย (อ่านอย่างเดียว)

ดู Google Cloud ผ่าน gcloud CLI: บัญชีที่ login, project ที่เลือก, VM ที่รันอยู่, Cloud Run service, สถานะ billing สลับ project ได้ และมี template คำสั่งที่ใช้บ่อยให้ copy (deploy Cloud Run, สร้าง/หยุด VM, เก็บ secret, ดู log) หรือส่งงานให้ Claude ทำ
| คำสั่ง | ทำอะไร |
|---|---|
/gcloud status | บัญชี project region |
/gcloud projects | project ทั้งหมด |
/gcloud use <project-id> | สลับ project |
/gcloud vms / run / billing | VM, Cloud Run, billing |
/gcloud template [ชื่อ] | template คำสั่ง |
/gcloud ask <งาน> | ให้ Claude ทำงาน GCP ให้ |
/plugin install gcloud-th --marketplace tksoft11/claude-code-mods-th
ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)
เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:
ต้องติดตั้ง gcloud CLI และ gcloud auth login ก่อน Mod เรียก gcloud เฉพาะคำสั่งอ่าน (ยกเว้น /gcloud use ที่เปลี่ยน project ในเครื่อง) ไม่สร้าง ลบ หรือ deploy เอง เงื่อนไข free tier เปลี่ยนได้ ดูล่าสุดที่ https://cloud.google.com/free
hooks/register.tsx 165 lines1/**
2 * ============================================================================
3 * MOD 39: GCloud TH - ดู Google Cloud ผ่าน gcloud CLI (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: Google Cloud Console และ gcloud CLI
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. ดูสถานะ: บัญชีที่ login, project ที่เลือก, region เริ่มต้น
9 * 2. ดูของที่รันอยู่: VM (Compute Engine), Cloud Run, และสถานะ billing ของ project
10 * 3. สลับ project ที่ใช้
11 * 4. Template คำสั่งที่ใช้บ่อย (deploy Cloud Run, สร้าง VM, เก็บ secret) แบบ copy ไปใช้
12 * 5. /gcloud ask ให้ Claude ทำงาน GCP ให้ (Claude จะขออนุญาตก่อนรันคำสั่งทุกครั้ง)
13 *
14 * ความปลอดภัย: Mod เรียก gcloud เฉพาะคำสั่งอ่านอย่างเดียว ไม่สร้าง ลบ หรือ deploy เอง
15 * ต้องติดตั้ง gcloud CLI และ gcloud auth login ก่อน
16 */
17
18import type { EngineInterface, Register } from 'claude-code'
19
20let isWindows = false
21
22// ==========================================================================
23// บทที่ 1: เรียก gcloud แล้วอ่านผลเป็น JSON (--format=json อ่านง่ายกว่าข้อความ)
24// ==========================================================================
25// Windows ติดตั้ง gcloud เป็น gcloud.cmd จึงต้องเรียกผ่าน cmd /c; argument เป็นค่าคงที่หรือผ่านการตรวจแล้ว
26async function gcloud($: EngineInterface, args: string[]): Promise<unknown> {
27 const argv = isWindows ? ['cmd', '/c', 'gcloud', ...args, '--format=json'] : ['gcloud', ...args, '--format=json']
28 const r = await $.process.run(argv, { timeoutMs: 60_000 }).catch(err => {
29 throw new Error(`เรียก gcloud ไม่ได้: ${err instanceof Error ? err.message : String(err)}`)
30 })
31 if (r.exitCode !== 0) {
32 if (/not recognized|not found|No such file/i.test(r.stderr)) throw new Error('NOT_INSTALLED')
33 throw new Error(r.stderr.trim().split('\n').slice(-2).join(' ') || `gcloud exit ${r.exitCode}`)
34 }
35 return r.stdout.trim() ? JSON.parse(r.stdout) : null
36}
37
38const NOT_INSTALLED = `☁️ ยังไม่พบ gcloud CLI
391. ติดตั้ง: https://cloud.google.com/sdk/docs/install
402. รัน gcloud auth login แล้วเปิด Claude Code ใหม่
413. /gcloud status`
42
43function validProject(id: string | undefined): id is string {
44 return /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(id ?? '')
45}
46
47// ==========================================================================
48// บทที่ 2: คำสั่งอ่านอย่างเดียว
49// ==========================================================================
50async function status($: EngineInterface): Promise<string> {
51 const cfg = (await gcloud($, ['config', 'list'])) as { core?: { account?: string; project?: string }; compute?: { region?: string; zone?: string }; run?: { region?: string } }
52 return [
53 '☁️ Google Cloud',
54 ` บัญชี ${cfg.core?.account ?? '(ยังไม่ login: gcloud auth login)'}`,
55 ` project ${cfg.core?.project ?? '(ยังไม่เลือก: /gcloud use <project-id>)'}`,
56 ` region compute ${cfg.compute?.region ?? '-'} / run ${cfg.run?.region ?? '-'}`,
57 ].join('\n')
58}
59
60async function projects($: EngineInterface): Promise<string> {
61 const list = (await gcloud($, ['projects', 'list'])) as { projectId: string; name: string; lifecycleState?: string }[]
62 return list.length ? ['📁 Projects', ...list.map(p => ` ${p.projectId.padEnd(30)} ${p.name}${p.lifecycleState && p.lifecycleState !== 'ACTIVE' ? ` (${p.lifecycleState})` : ''}`)].join('\n') : 'ไม่มี project'
63}
64
65async function vms($: EngineInterface): Promise<string> {
66 const list = (await gcloud($, ['compute', 'instances', 'list'])) as { name: string; status: string; zone: string; machineType: string }[]
67 if (list.length === 0) return '🖥️ ไม่มี VM ใน project นี้'
68 return ['🖥️ Compute Engine', ...list.map(v => ` ${v.status === 'RUNNING' ? '🟢' : '⚪'} ${v.name.padEnd(24)} ${v.machineType.split('/').pop()} · ${v.zone.split('/').pop()} · ${v.status}`), '', '💡 VM ที่ไม่ใช้ หยุดไว้จะไม่เสียค่า CPU (แต่ยังเสียค่าดิสก์)'].join('\n')
69}
70
71async function runServices($: EngineInterface): Promise<string> {
72 const list = (await gcloud($, ['run', 'services', 'list'])) as { metadata?: { name?: string; labels?: Record<string, string> }; status?: { url?: string; conditions?: { type: string; status: string }[] } }[]
73 if (list.length === 0) return '🏃 ไม่มี Cloud Run service'
74 return ['🏃 Cloud Run', ...list.map(s => ` ${s.status?.conditions?.find(c => c.type === 'Ready')?.status === 'True' ? '🟢' : '🔴'} ${s.metadata?.name ?? '-'} ${s.status?.url ?? ''}`)].join('\n')
75}
76
77async function billing($: EngineInterface): Promise<string> {
78 const cfg = (await gcloud($, ['config', 'list'])) as { core?: { project?: string } }
79 const project = cfg.core?.project
80 if (!validProject(project)) return 'ยังไม่ได้เลือก project /gcloud use <project-id>'
81 const info = (await gcloud($, ['billing', 'projects', 'describe', project])) as { billingEnabled?: boolean; billingAccountName?: string }
82 return `💳 ${project}: billing ${info.billingEnabled ? `เปิด (${info.billingAccountName ?? ''})` : 'ปิด'}\n💡 ตั้งงบและการแจ้งเตือนที่ https://console.cloud.google.com/billing/budgets กันบิลบาน`
83}
84
85// ==========================================================================
86// บทที่ 3: Template คำสั่งที่ใช้บ่อย (แสดงให้ copy ไม่รันเอง)
87// ==========================================================================
88const TEMPLATES: Record<string, { name: string; cmd: string; note: string }> = {
89 run: { name: 'Deploy Cloud Run จากโค้ด', cmd: 'gcloud run deploy my-app --source . --region=asia-southeast1 --allow-unauthenticated', note: 'จ่ายตามที่ใช้ มี free tier รายเดือน เหมาะกับเว็บ/API' },
90 vm: { name: 'สร้าง VM เล็กสุด', cmd: 'gcloud compute instances create my-vm --machine-type=e2-micro --zone=us-central1-a --image-family=debian-12 --image-project=debian-cloud', note: 'e2-micro อยู่ใน Always Free เฉพาะบาง region ในสหรัฐฯ (เช่น us-central1) region อื่นเสียเงิน' },
91 stop: { name: 'หยุด VM', cmd: 'gcloud compute instances stop my-vm --zone=us-central1-a', note: 'หยุดแล้วไม่เสียค่า CPU/RAM' },
92 secret: { name: 'เก็บ secret', cmd: 'printf "ค่าลับ" | gcloud secrets create my-api-key --data-file=-', note: 'อย่าเก็บ key ไว้ในโค้ด ใช้ Secret Manager แทน' },
93 logs: { name: 'ดู log ของ Cloud Run', cmd: 'gcloud run services logs read my-app --region=asia-southeast1 --limit=50', note: 'ดู error ล่าสุดของ service' },
94 apis: { name: 'เปิด API ที่ต้องใช้', cmd: 'gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com', note: 'ต้องเปิดก่อน deploy Cloud Run ครั้งแรก' },
95}
96
97const HELP = `☁️ GCloud TH (Mod 39)
98========================================
99/gcloud status บัญชี project region
100/gcloud projects project ทั้งหมด
101/gcloud use <project-id> สลับ project
102/gcloud vms | run | billing VM, Cloud Run, สถานะ billing
103/gcloud template [ชื่อ] ${Object.keys(TEMPLATES).join(' ')}
104/gcloud ask <งาน> ให้ Claude ทำงาน GCP ให้ (ขออนุญาตก่อนรันทุกคำสั่ง)
105free tier ล่าสุด: https://cloud.google.com/free`
106
107export const register: Register = on => {
108 on('session.start', async ($, e, next) => {
109 isWindows = /^[A-Za-z]:/.test(e.cwd)
110 await $.command.register({ name: 'gcloud', description: 'ดู Google Cloud: project VM Cloud Run billing + template คำสั่ง (/gcloud help)', argumentHint: 'status|projects|use|vms|run|billing|template|ask' })
111 return next(e)
112 })
113
114 // ==========================================================================
115 // บทที่ 4: คำสั่ง /gcloud
116 // ==========================================================================
117 on('command.run', { command: 'gcloud' }, async ($, e) => {
118 const [sub = 'status', ...rest] = e.args.trim().split(/\s+/).filter(Boolean)
119 try {
120 switch (sub) {
121 case 'status':
122 return { text: await status($) }
123 case 'projects':
124 return { text: await projects($) }
125 case 'use': {
126 if (!validProject(rest[0])) return { text: 'วิธีใช้: /gcloud use my-project-123 (ดู id ด้วย /gcloud projects)' }
127 await gcloud($, ['config', 'set', 'project', rest[0]])
128 return { text: `✅ ใช้ project ${rest[0]} แล้ว` }
129 }
130 case 'vms':
131 return { text: await vms($) }
132 case 'run':
133 return { text: await runServices($) }
134 case 'billing':
135 return { text: await billing($) }
136 case 'template': {
137 const t = TEMPLATES[rest[0] ?? '']
138 if (!t) return { text: ['📋 Template', ...Object.entries(TEMPLATES).map(([k, v]) => ` ${k.padEnd(7)} ${v.name}`), '', '/gcloud template run'].join('\n') }
139 return { text: `📋 ${t.name}\n\n ${t.cmd}\n\n💡 ${t.note}\nแก้ชื่อ/region ก่อนรัน หรือให้ Claude ทำ: /gcloud ask ${t.name}` }
140 }
141 case 'ask': {
142 const task = rest.join(' ')
143 if (!task) return { text: 'วิธีใช้: /gcloud ask deploy โฟลเดอร์นี้ขึ้น Cloud Run region สิงคโปร์' }
144 $.clock.after(50, () => void $.prompt.submit({ text: `ใช้ gcloud CLI ทำงานนี้บน Google Cloud: ${task}\nตรวจ project และ region ปัจจุบันก่อน อธิบายคำสั่งที่จะรันและค่าใช้จ่ายที่อาจเกิด แล้วค่อยรัน` }).catch(() => undefined)) // ส่งหลังคำสั่งตอบกลับแล้ว (ส่งระหว่าง command.run ไม่ได้)
145 return { text: `☁️ ส่งงานให้ Claude แล้ว: ${task}` }
146 }
147 default:
148 return { text: HELP }
149 }
150 } catch (err) {
151 const msg = err instanceof Error ? err.message : String(err)
152 return { text: msg === 'NOT_INSTALLED' ? NOT_INSTALLED : `❌ ${msg}` }
153 }
154 })
155}
156
157/**
158 * ========================================================================
159 * การบ้าน / ต่อยอด
160 * ========================================================================
161 * 1. แจ้งเตือนเมื่อมี VM รันทิ้งไว้เกิน 8 ชม. (เช็คด้วย $.clock.every)
162 * 2. แผงสรุปค่าใช้จ่ายรายเดือนจาก BigQuery billing export
163 * 3. เทียบเวลา deploy และ revision ของ Cloud Run
164 */
165