ui.render setiap kali akan menggambar render site, dan hook Anda untuk event tersebut mengembalikan apa yang akan digambar di sana.
Peta ini menunjukkan di mana mod dapat menggambar dalam sesi terminal:
Untuk mencari satu prop atau batas, lihat referensi.
Bangun panel dengan tab
Di bagian ini Anda membangun mod yang menambahkan perintah/hello-tabs, dan perintah membuka panel. Panel adalah sidebar di samping transkrip dalam terminal fullscreen yang lebar, atau wilayah berbingkai di atas prompt sebaliknya. Panel ini menampilkan dua tab, dan tab kedua memiliki tombol yang menambah satu ke penghitung. Hitungan masih ada setelah Anda memulai ulang Claude Code.
Mod yang selesai terlihat seperti ini. Rekaman membuka panel, beralih ke tab kedua, menekan tombol beberapa kali, dan kembali ke tab pertama:
1
Buat plugin
Mod adalah plugin dengan manifest, Beri nama titik masuk Anda di
hooks.json yang menunjuk ke kode Anda, dan file kode. Buat mod menjelaskan masing-masing. Buat direktori bernama hello-tabs dengan direktori .claude-plugin dan hooks di dalamnya, kemudian simpan dua file pertama.Simpan manifest sebagai hello-tabs/.claude-plugin/plugin.json:hello-tabs/.claude-plugin/plugin.json
hello-tabs/hooks/hooks.json:hello-tabs/hooks/hooks.json
2
Tulis kodenya
Kode melakukan tiga pekerjaan, satu di setiap hook:Setiap hook juga melakukan sesuatu yang tidak jelas dari kode:
- Menambahkan perintah
/hello-tabs - Membuka panel saat Anda menjalankan perintah tersebut
- Menggambar konten panel: baris tab dan badan tab yang terbuka
tab dan count, menyimpan status panel.Simpan ini sebagai hello-tabs/hooks/register.js:hello-tabs/hooks/register.js
session.startjuga membaca hitungan yang disimpan dari$.store, penyimpanan kunci-nilai yang bertahan antar sesi.command.runhanya memberi tahu Claude Code bahwa panel ada. Membuka panel tidak menggambar apa pun dengan sendirinya: Claude Code kemudian menaikkanui.renderuntuk menanyakan apa yang ada di dalamnya.ui.rendermengembalikan pohon elemen,Boxyang menyimpan kotak lain, teks, dan tombol, dan membangunnya lagi daritabdancountsetiap kali berjalan.
onPress-nya, yang mengubah variabel dan memanggil redraw. Claude Code kemudian menjalankan hook ui.render lagi, dan hook membangun pohon baru dari nilai baru. Setiap gambar interaktif menggunakan siklus render itu: callback mengubah status, dan hook merender lagi dari status baru.3
Buka panel
Di shell Anda, mulai Claude Code dengan
claude --plugin-dir ./hello-tabs. Di prompt Claude Code, jalankan /hello-tabs. Panel terbuka dengan 1: One dan 2: Two di seluruh bagian atas. Tekan 2, kemudian tekan a, pintasan keyboard untuk Add one, beberapa kali. Hitungan naik.4
Periksa bahwa hitungan disimpan
Tekan Esc untuk menutup panel, kemudian keluar dari sesi. Di shell Anda, mulai Claude Code lagi dengan perintah
claude --plugin-dir ./hello-tabs yang sama, dan di prompt Claude Code jalankan /hello-tabs. Hitungan ada di mana Anda meninggalkannya.Untuk menghapus hitungan, buat mod memanggil $.store.delete('count'). Jaga status mencakup berapa lama setiap jenis nilai bertahan.Pilih di mana menggambar
Hookui.render berjalan untuk setiap render site kecuali Anda mempersempit ke yang Anda inginkan. Untuk memilih render site, teruskan filter, disebut matcher, sebagai argumen kedua ke on. { component: 'Pane' } menjalankan hook hanya untuk panel. Dalam hook, e.component menamai situs, e.surface mengatakan aplikasi mana yang menggambar, dan e.props menyimpan data situs sendiri. Untuk panel, e.requestId adalah id yang Anda buka dengannya.
Dua situs kosong sampai mod mengisinya, panel dan pita. Pilih tab untuk melihat apa itu masing-masing dan cara menggambar di dalamnya:
- Pane
- Band above the prompt
Panel adalah sidebar di samping transkrip dalam terminal fullscreen yang lebar, atau wilayah berbingkai di atas prompt sebaliknya. Dengan beberapa panel terbuka, masing-masing mendapat tab yang menampilkan judulnya.Panel muncul saat mod Anda memanggil
$.ui.open dengan id yang Anda pilih, seperti dalam $.ui.open({ id: 'hello-tabs' }). Buka panel pada waktu yang tepat mencakup bidang lain dan kapan panel menunggu terminal yang lebih lebar.Untuk menggambar di panel Anda, filter pada { component: 'Pane' } dan periksa bahwa e.requestId adalah id Anda.Ubah apa yang sudah digambar Claude Code
Claude Code menggambar sebagian besar antarmukanya sendiri: pesan, baris panggilan alat, spinner, dan lainnya. Masing-masing bagian itu adalah render site juga, jadi mod dapat mengubah gaya atau menggantinya. Untuk mengubah satu, filter hookui.render Anda pada namanya dari tabel ini:
Di situs yang sudah digambar Claude Code, hook Anda memiliki tiga pilihan: ubah detail, ganti gambar, atau biarkan saja. Pilih tab untuk melihat masing-masing diterapkan pada spinner. Contoh membaca variabel
calls yang hook lain hitung, seperti dalam mod tutorial.
- Change a detail
- Replace the drawing
- Leave it alone
Untuk menjaga gambar Claude Code dan mengubah satu bagian darinya, teruskan Spinner menjaga animasi dan katanya, dan teks Anda mengikuti kata:
next salinan event dengan props yang diubah. Hook ini mengubah teks setelah kata spinner:AskUserQuestion, adalah satu, jadi mod dapat mengubah itu.
Terminal dan aplikasi Desktop tidak menaikkan semua situs yang sama. Pane, AbovePrompt, Spinner, dan situs transkrip bekerja di keduanya. Beberapa baris status lainnya hanya diangkat di terminal. Tabel render sites mencantumkan di mana masing-masing diangkat.
Buka panel pada waktu yang tepat
Panel hanya muncul saat mod Anda membukanya. Bagaimana dan kapan Anda membukanya menentukan apakah itu mengambil fokus keyboard, berapa banyak ruang yang dimintanya, dan apakah itu menampilkan sama sekali di terminal yang sempit. Untuk membuka panel, panggil$.ui.open dengan id yang Anda pilih. id adalah nama panel: hook ui.render Anda memeriksanya, dan Anda meneruskannya lagi untuk menutup panel.
$.ui.close dengan id yang Anda buka dengannya:
id, $.ui.open mengambil bidang opsional ini:
Untuk membiarkan perintah membuka panel saat Claude bekerja, tambahkan
immediate: true saat Anda mendaftarkan perintah. Tanpanya, perintah yang diketik selama giliran menunggu giliran berakhir.
Ketika panel menunggu terminal yang lebih lebar
Panel yang dibuka mod Anda tanpa diminta tidak muncul di terminal sempit, jadi tidak dapat mengambil alih layar kecil. Apakah itu muncul tergantung pada apa yang membukanya:- Dibuka oleh sesuatu yang dilakukan pengguna, seperti perintah yang mereka jalankan atau tombol yang mereka tekan, panel muncul pada lebar apa pun
- Dibuka oleh mod Anda bertindak sendiri, seperti dari timer atau hook
turn.start, panel hanya muncul di terminal setidaknya 144 kolom lebar. Setelah pengguna telah membuka panel itu sendiri sekali, 110 kolom cukup.
$.ui.open diselesaikan ke { isPlaced: true }. Ketika panel menunggu, isPlaced adalah false dan reason adalah string yang mengatakan mengapa. Panel yang menunggu muncul saat pengguna membukanya atau memperlebar terminal. Untuk mengatakan sesuatu tersedia tanpa membuka panel, panggil $.ui.toast('Your message'), yang menampilkan pemberitahuan kecil yang hilang setelah beberapa detik.
Bangun pohon dari elemen
Apa yang dikembalikan hookui.render adalah pohon elemen: deskripsi apa yang akan digambar, terbuat dari kotak, teks, dan kontrol bersarang di dalam satu sama lain. Anda mendeskripsikan gambar, dan Claude Code merender di terminal atau aplikasi Desktop.
Untuk mendapatkan elemen, panggil $.ui.resolve(e) dalam hook Anda, seperti dalam const { Box, Text, Button } = $.ui.resolve(e). Setiap elemen adalah fungsi. Anda meneruskan prop, dan Anda menempatkan elemen dan string yang ada di dalamnya dalam children.
Sebagian besar gambar menggunakan empat elemen. Pilih tab untuk melihat masing-masing dan cara terminal menggambarnya:
- Text
- Box
- Input
Text menggambar string, dengan gaya opsional seperti bold dan color:
Jika modul Anda adalah file
.tsx atau .jsx, Anda dapat menulis pohon sebagai JSX. Dekonstruksi elemen dari $.ui.resolve(e) terlebih dahulu, karena modul hooks tidak memiliki global elemen.
Jika pohon menggunakan elemen yang tidak dimiliki aplikasi, prop yang tidak diambil elemen, atau anak di mana tidak ada, Claude Code menggambar versinya sendiri dari situs.
Dalam sesi yang dimulai dengan --plugin-dir, baris transkrip mengatakan demikian, seperti ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own. Debug log mencatatnya sebagai ui.render (Pane): a hook returned a tree that does not validate dengan alasan yang sama. Tidak ada yang lain muncul dalam sesi, jadi ketika gambar tidak muncul, periksa baris itu atau log.
Gambar grid sel berwarna
Untuk peta panas, sparkline, atau papan permainan di terminal, gambar satuRaster dan bukan Box untuk setiap sel. Raster mengambil key, ukurannya dalam columns dan rows, dan cells, yang mengemas setiap sel menjadi satu string. Setiap sel adalah tiga angka: titik kode karakter, warnanya, dan warna latar belakangnya. Warna adalah angka heksadesimal dengan dua digit masing-masing untuk merah, hijau, dan biru, seperti 0xc62828 untuk merah, atau 0x01000000 untuk default terminal.
Aplikasi Desktop tidak memiliki Raster, jadi periksa e.surface dan gambar teks di sana. Badan panel ini menggambar peta panas tiga kali dua:
rows adalah bagian yang akan Anda ubah, dan cellsOf mengubahnya menjadi string yang dikemas. Hook menggambar hanya di panel yang id-nya adalah heat, jadi buka satu dengan $.ui.open({ id: 'heat' }) dari perintah, seperti contoh hello-tabs membuka panelnya.
Setiap karakter harus lebar satu sel. Untuk menganimasikan Raster yang sudah di layar, panggil $.ui.blit dengan id panel sebagai requestId, key Raster, ukuran yang sama, dan sel baru. Untuk contoh ini, itu adalah $.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) }). Itu melukis ulang elemen itu saja tanpa menjalankan hook ui.render Anda lagi.
Merespons penekanan dan pengetikan
Ketika pengguna menekan tombol, mengetik ke bidang, atau memilih dari daftar yang digambar mod Anda, Claude Code memanggil fungsi yang Anda berikan kontrol itu, dan itu berjalan dalam modul Anda. Setiap kontrol mengambil callback-nya sendiri:Button: mengambilonPress(e), di manae.surfaceadalah aplikasi tempat penekanan berasalInput: mengambilonSubmit(value)danonInput(value)Select: mengambilonSelect(value)dengan pilihan dalamoptions, daftar setidaknya satu pilihan dengan nilai unik, seperti[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]
key-nya, jadi berikan masing-masing satu. Setiap penggunaan kontrol juga menembakkan ui.press, ui.input, atau ui.select dengan key dalam e.element, dan mod lain dapat menghubungkan event tersebut. Hook-nya berjalan sebelum callback Anda, jadi itu melihat apa yang pengguna ketik ke Input Anda dan dapat mengubahnya atau menjawab sebagai pengganti callback Anda. API mods tidak memiliki metode yang menekan tombol mod lain.
Fokus keyboard dan pintasan keyboard
Mod Anda tidak pernah membaca keyboard itu sendiri. Pengguna menekan kunci, Claude Code memutuskan kontrol mana yang dimaksudkan, dan callback kontrol itu berjalan. Terlepas dari pintasan keyboard digit di pita, itu hanya terjadi saat panel atau pita Anda memiliki fokus keyboard. Sisa waktu, kunci pergi ke prompt.Bagaimana panel mendapat fokus keyboard
Panel mendapat fokus keyboard dalam salah satu dari tiga cara:- Mod Anda membukanya dengan
focus: truedari perintah atau penekanan - Pengguna menekan Ctrl+X kemudian Tab
- Pengguna mengkliknya
focus: true hanya saat prompt kosong dan tidak ada yang lain memiliki fokus keyboard. Panel yang terbuka saat pengguna mengetik tidak mengambil keystroke mereka.
Apa yang dilakukan setiap kunci
Tabel ini mencantumkan apa yang dilakukan kunci saat panel atau pita Anda memiliki fokus keyboard:
Mod tidak dapat mengikat Tab atau tombol panah ke apa pun yang lain, jadi permainan mengarahkan dengan
w, a, s, dan d.
Atur pintasan keyboard dan fokus pertama
Dua prop pada kontrol memutuskan bagaimana keyboard mencapainya:hotkey: untuk membiarkan pengguna menekanButtondengan satu kunci, berikanhotkeydari satu digit atau satu huruf kecil, seperti dalamhotkey: 'a'autoFocus: untuk memilih kontrol mana yang memiliki fokus saat panel terbuka, tambahkanautoFocus: trueke dalamnya. Tinggalkan prop dari yang lain, karena Claude Code menolakautoFocus: false.
Di terminal, beri nama kunci dalam label tombol berbingkai, atau gunakan
plain: true, sehingga pengguna dapat melihat apa yang harus ditekan. Referensi elemen memiliki aturan Button lainnya: action, pintasan keyboard digit di pita, dan dua tombol pada satu pintasan keyboard.
Ambil input yang diketik dan gambar baris untuk setiap item
Banyak panel adalah bidang teks dengan daftar di bawahnya. Contoh di bagian ini adalah panel catatan: Anda mengetik catatan dan menekan Enter untuk menambahkannya, dan setiap catatan memiliki tombolx yang menghapusnya. Dengan dua catatan ditambahkan, terminal menggambar panel dengan cara ini:
- Ambil input yang diketik:
InputmemanggilonSubmit(value)dengan teks bidang saat pengguna menekan Enter, danonInput(value)pada setiap perubahan - Gambar daftar: petakan data Anda ke satu baris masing-masing, dan berikan setiap tombol baris
key-nya sendiri
- Tambahkan catatan: ketik baris dan tekan Enter. Baris muncul sebagai baris baru, dan bidang kosong.
- Hapus catatan: tekan Tab sampai tombol
xcatatan memiliki fokus, kemudian tekan Enter.xadalah label tombol dan bukan pintasan keyboard, jadi mengetik huruf tidak menekan itu.
hello-tabs: callback mengubah notes, memanggil redraw, dan menyimpan daftar ke $.store.
Bidang kosong setelah setiap submit karena prop value-nya. value adalah teks yang dipegang bidang saat digambar, dan pengetikan pengguna menggantinya sampai hook Anda menggambar bidang lagi. Contoh selalu menggambar bidang dengan ''.
Contoh menyimpan catatan dan tidak memuatnya. Untuk membawanya kembali di sesi berikutnya, bacalah dalam hook session.start, cara hello-tabs membaca count.
Tiga prop membuat baris bidang, Note: Type a note and press Enter ⏎ add:
Mengirimkan
Input tidak memulai giliran kecuali callback Anda memanggil $.prompt.submit.
Gambar ulang situs
Gambar adalah snapshot: itu menunjukkan apa yang dikembalikan hookui.render Anda terakhir kali hook berjalan. Untuk menampilkan sesuatu yang baru, hook harus berjalan lagi. Claude Code menjalankannya lagi untuk beberapa perubahan, dan mod Anda meminta sisanya.
Ketika Claude Code menggambar ulang tanpa diminta
Claude Code menjalankan hookui.render Anda lagi ketika prop situs berubah atau lebar terminal berubah. Itu tidak menjalankan hook pada timer, dan itu tidak dapat mengatakan ketika variabel dalam modul Anda berubah.
Gambar ulang saat data Anda berubah
Untuk memiliki situs Anda digambar lagi setelah data Anda sendiri berubah, panggil$.ui.invalidate('ui.render'). Panel ini menghitung penekanan. Callback tombol mengubah count, kemudian meminta redraw:
hello-tabs membungkus panggilan yang sama dalam fungsi redraw-nya.
Nilai yang Anda simpan dalam $.state tidak memerlukan panggilan, karena menulis nilai menggambar ulang situs yang membacanya.
Gambar ulang pada timer
Untuk menjaga jam, hitung mundur, atau nilai dari luar sesi saat ini, gambar ulang sesuai jadwal. Mulai timer dalam hooksession.start modul. Jika modul sudah memiliki satu, seperti hello-tabs, tambahkan baris $.clock.every ke dalamnya:
ui.render Anda sekali per detik. Timer berhenti saat modul dimuat ulang, dan salinan baru modul memulai miliknya sendiri.
Seberapa sering situs dapat digambar ulang
Claude Code membatasi seberapa sering itu menggambar ulang situs, jadi mod Anda dapat memanggil$.ui.invalidate sesering data berubah. Panel yang terlihat dan pita memiliki batas yang lebih tinggi daripada situs lainnya, dan tabel batas memiliki angkanya.
Panggilan yang datang lebih cepat dari batas digabungkan menjadi satu redraw. Redraw itu menjalankan hook Anda sekali, dan hook membaca data Anda seperti adanya saat itu, jadi nilai terbaru ditampilkan dan nilai di antaranya tidak. Animasi tidak dapat berjalan lebih cepat dari batas.
Jaga status
Mod memiliki tiga tempat untuk menyimpan nilai, dan mereka berbeda dalam berapa lama nilai bertahan: sampai modul dimuat ulang, sampai sesi berakhir, atau dari satu sesi ke sesi berikutnya. Pilih berdasarkan berapa lama nilai harus bertahan:$.store.get(key) diselesaikan ke nilai atau undefined, dan $.store.set(key, value) mengambil nilai JSON apa pun.
Simpan nilai dalam $.state
$.state menyimpan nilai untuk panjang sesi, dan itu menggambar ulang untuk Anda. Ini adalah status reaktif: hook ui.render yang membaca nilai berlangganan ke dalamnya, jadi Claude Code menggambar ulang situs itu setiap kali Anda menulis nilai, dan Anda tidak memanggil $.ui.invalidate. Nilai dalam $.state juga bertahan reload modul, yang variabel tidak.
Untuk mengaturnya, deklarasikan nilai Anda, arahkan manifest Anda ke deklarasi, kemudian tentukan dan gunakan setiap nilai. Contoh memindahkan count dari hello-tabs ke dalam $.state.
Deklarasikan nilai
Deklarasikan nilai dalam file tipe. Kunci luar adalah nama plugin Anda, dan setiap entri di bawahnya adalah nilai dan tipenya. Simpan ini sebagaihello-tabs/types/index.d.ts:
hello-tabs/types/index.d.ts
Arahkan manifest ke deklarasi
Untuk membiarkanclaude plugin validate memeriksa kode Anda terhadap file itu, tambahkan bidang types ke manifest dengan jalurnya:
hello-tabs/.claude-plugin/plugin.json
Tentukan, baca, dan tulis nilai
Dalam modul Anda, tentukan setiap nilai dengan default, bacalah saat menggambar, dan tulislah dari callback.atom menamai nilai dan defaultnya, read mengembalikannya, dan update menulisnya. Tiga pembantu memanggil $.state.get dan $.state.set untuk Anda:
ui.render membaca count, Claude Code menjalankan hook lagi setiap kali tombol menulisnya.
Tiga aturan berlaku untuk kode:
- Tulis
plugindankeysebagai string literal:claude plugin validatemembacanya dari sumber Anda - Deklarasikan setiap nilai dalam file tipe: jika tidak, validasi gagal dengan
hello-tabs.count is not declared - Tulis dari callback atau hook event lain: hook
ui.renderdapat membaca status dan tidak dapat menulisnya, jadi tulis darionPress,onSubmit, atau hook untuk event lain
Ubah hello-tabs untuk menggunakan $.state
Untuk memindahkan count dalam hello-tabs ke dalam $.state, ubah setiap baris yang menggunakannya:
- Di atas modul: tambahkan baris
import, dan gantilet count = 0dengan barisatom - Dalam hook
ui.render: tambahkan barisreadsebelumtabButton, dan gambar'Count: ' + ndalamText - Dalam tombol Add one: ganti
onPressdengan yang dalam Simpan dari lebih dari satu sesi, yang menyimpan hitungan serta menulisnya - Dalam hook
session.start: ganti dua baris yang membacasaveddengan panggilanloadCountdari Muat nilai yang disimpan lagi setelah/clear
redraw untuk tombol tab, karena tab masih variabel.
Muat nilai yang disimpan lagi setelah /clear
Jika mod Anda menyalin nilai yang disimpan dari $.store ke dalam $.state pada session.start, itu harus menyalinnya lagi setelah /clear, /resume, atau /branch. Perintah tersebut mengembalikan setiap nilai $.state ke defaultnya, dan session.start tidak dipecat lagi. classic.SessionStart dipecat setelah masing-masing, dengan e.source diatur ke clear, resume, atau fork, jadi salin nilai lagi dalam hook di atasnya. Jika tidak, gambar Anda menampilkan default, dan callback yang menyimpan nilai $.state menulis default di atas apa yang Anda simpan.
Kode ini memuat count dari kedua hook. Itu dibangun di atas versi $.state dari hello-tabs, di mana count adalah atom dan update diimpor. Letakkan loadCount di atas register, dan tambahkan panggilan loadCount ke hook session.start yang sudah Anda miliki. classic.SessionStart juga dipecat saat startup dan setelah pemadatan, yang tidak mengatur ulang $.state, jadi filter pada source menjaga hook ke tiga reset:
/clear dan bukan 0, dan penekanan berikutnya dari Add one menambah ke hitungan yang disimpan.
loadCount menulis nilai yang disimpan di atas yang ada dalam $.state, dan session.start dipecat lagi setiap kali modul dimuat ulang. Untuk menjaga penyimpanan agar tidak tertinggal, simpan pada setiap perubahan, seperti yang dilakukan tombol Add one.
Untuk memeriksa reload tanpa sesi, uji gambar setelah /clear.
Simpan dari lebih dari satu sesi
Setiap sesi di mesin Anda yang menjalankan mod Anda berbagi satu$.store. get diikuti oleh set bukan atomik. Ketika dua sesi masing-masing membaca nilai, mengubahnya, dan menulisnya kembali, mereka bersaing, dan penulisan kedua menggantikan yang pertama.
Dua pilihan membuat itu kurang mungkin:
- Berikan setiap item kuncinya sendiri:
setmengubah hanya kuncinya sendiri, jadi sesi yang menulis kunci berbeda tidak menimpa satu sama lain - Baca lagi tepat sebelum Anda menulis: untuk nilai yang beberapa sesi ubah,
getkunci dalam callback dan bangun nilai baru dari itu, bukan dari salinan yang Anda muat padasession.start. Penulisan sesi lain masih hilang jika mendarat antaragetdansetAnda.
Langkah berikutnya
- Bereaksi terhadap event: umpan gambar Anda dari panggilan alat dan giliran
- Gunakan API mods: umpan gambar Anda dari timer dan panggilan model
- Uji gambar: tekan tombol Anda dari tes, di lebih dari satu permukaan
- Render sites dan elemen: prop setiap situs dan prop setiap elemen