Bongkar Claude Code Mods: Hook, Event, dan 15 Bagian Layar yang Bisa Diganti
Panduan dari nol buat mod Claude Code: rantai hook, kapan tiap event dipanggil, bagian layar yang bisa digambar ulang, remote $, delapan contoh kasus, dan daftar lengkap 46 event.
Husni Adil Makmur, . 15 menit baca, 2.819 kata.
Claude Code, Claude Code Mods, AI Agents, TypeScript, Developer Tools, Deep Dive
Mod Itu Apa?
Mod itu satu file TypeScript yang nyelip di tengah kerjaan Claude Code. Setiap kali engine mau ngelakuin sesuatu, misalnya jalanin tool, gambar layar, atau ngirim prompt, mod kamu dapet giliran duluan. Dia bisa cuma ngintip, ngubah dulu, atau jawab sendiri tanpa nerusin ke engine.
Yang bikin saya kaget: mod bisa ngegambar ulang tampilan bawaan Claude Code. Tiap tool call bisa diganti jadi satu baris, jawaban model bisa digambar ulang pakai gaya lain, dan blok hasil tool bisa disembunyiin. Ini jauh lebih dalam dari sekadar nambah status line atau popup.
Post ini panduan dari nol. Semua nama event, method, dan props di sini saya ambil dari type declaration yang ditulis Claude Code 2.1.294 sendiri. API-nya masih berlabel early access, jadi detailnya bisa berubah di rilis berikutnya.
Konsep Inti: Rantai Hook
Kalau cuma boleh ingat satu hal dari post ini, ingat gambar ini.
Satu event lewat semua mod dulu, baru sampai ke perilaku bawaan engine. Hasilnya naik lagi lewat jalur yang sama. Jadi setiap mod bisa ngubah input sebelum engine jalan, dan bisa baca atau ngubah hasilnya setelah engine selesai.
Kalau kamu pernah nulis middleware di Express atau Koa, bentuknya mirip banget.
Isinya Cuma Tiga File
Satu mod itu satu folder. Ga ada build step, engine langsung baca TypeScript-nya.
mod-saya/
├─ .claude-plugin/
│ └─ plugin.json nama, versi, deskripsi
└─ hooks/
├─ hooks.json nunjuk ke register.tsx
└─ register.tsx semua logika di sini
Dua file JSON-nya pendek:
// .claude-plugin/plugin.json
{ "name": "mod-saya", "version": "0.1.0", "description": "Toast tiap turn selesai" }
// hooks/hooks.json
{ "modules": ["./register.tsx"] }Dan ini mod paling kecil yang beneran jalan:
import type { Register } from "claude-code";
export const register: Register = (on, options) => {
// tiap turn selesai, munculin toast
on("turn.complete", async ($, e, next) => {
$.ui.toast("Turn selesai");
return next(e);
});
};Satu hal yang perlu diingat dari awal: mod jalan di environment-nya sendiri. Ga ada Node, ga ada DOM. import fs from "node:fs" ga bakal jalan, dan module yang pakai import() dinamis malah ga di-load sama sekali. Semua akses ke luar lewat $. Parameter options isinya nilai userConfig dari plugin.json.
Rumusnya Satu
Semua mod, sekecil atau segede apa pun, isinya panggilan on(...) kayak gini:
on("tool.call", { tool: "Bash" }, async ($, e, next) => {
// ...
});| Bagian | Artinya | Contoh |
|---|---|---|
"tool.call" | kapan | "turn.complete", "ui.render", "prompt.submit" |
{ tool: "Bash" } | yang mana, opsional | { component: "ToolUse" }, { command: "catatan" } |
($, e, next) => … | ngapain | fungsi hook-nya |
Hook-nya sendiri selalu nerima tiga hal:
| Parameter | Isinya |
|---|---|
$ | Remote ke engine. Layar, model, file, jam, session. Semua yang mod mau sentuh lewat sini. |
e | Isi event-nya, read-only. Buat tool.call isinya nama tool plus input-nya, misalnya e.command. |
next | Terusin ke mod berikutnya, ujungnya perilaku bawaan engine. Nilai baliknya hasil event itu. |
Empat Gerakan Hook
Dari tiga parameter itu, cuma ada empat hal yang bisa dilakuin sebuah hook.
1. Terusin Apa Adanya
Cuma ngintip, ga ngubah apa-apa. Paling sering dipakai buat nyatet atau ngitung.
return next(e);2. Ubah Dulu, Baru Terusin
Mod di bawahnya dan engine cuma ngelihat versi yang udah diubah.
return next({ ...e, command: e.command.trim() });3. Jawab Sendiri
Ga manggil next. Engine ga pernah jalanin perilaku bawaannya, dan yang di atas nerima jawaban dari hook ini.
return { deny: ".env ga boleh diedit" };4. Terusin, Terus Baca Hasilnya
Tunggu engine kelar, terus bereaksi ke hasilnya.
const ran = await next(e);
if (ran.isError) $.ui.status("gagal");
return ran;Satu jebakan penting: hook yang error di-skip, dan rantainya jalan terus. Buat mod tampilan, itu bagus, karena mod yang rusak ga bikin Claude Code ikut rusak. Buat mod penjaga, itu bahaya. Mod yang mestinya nolak rm -rf terus crash berarti command-nya tetap jalan. Makanya hook penjaga dikasih .catch:
on("tool.call", { tool: "Bash" }, guard).catch(($, e, next) =>
next.called ? next(e) : { deny: "guard-nya error" },
);next.called ngasih tau hook-nya udah sempet manggil next atau belum. Kalau belum, .catch nolak.
Kapan Mod Dipanggil
Ini urutan event dalam satu session, dari buka Claude sampai keluar. Klik tiap langkah buat lihat kapan event-nya jalan, bisa ngapain, dan contoh kodenya.
session.start jalan sekali per process buat tiap plugin, sebelum prompt pertama. Hot reload juga nembak event ini lagi.
Tempat yang pas buat daftarin slash command, buka pane, atau siapin data awal.
on("session.start", async ($, e, next) => {
await $.command.register({
name: "catatan",
description: "Buka pane catatan",
});
return next(e);
});prompt.submit jalan waktu kamu tekan Enter, sebelum turn mulai.
Teksnya bisa diubah pakai next({ ...e, text }), dan pesan di layar ikut berubah. Prompt-nya juga bisa dibatalin dengan balikin { drop: "alasan" }.
on("prompt.submit", async ($, e, next) =>
e.text.trim() === "tt"
? next({ ...e, text: "Jalanin semua test, terus benerin yang gagal" })
: next(e),
);turn.start jalan waktu turn model mulai, sebelum model call pertama.
Biasanya dipakai buat reset tampilan atau counter punya mod. Mod next-steps misalnya, nyembunyiin saran prompt yang lama di sini.
on("turn.start", async ($, e, next) => {
saran = []; // buang saran lama
$.ui.invalidate("ui.render"); // minta layar digambar ulang
return next(e);
});turn.step jalan di tiap request ke model dalam satu turn. Satu turn bisa punya banyak step: model jawab, pakai tool, jawab lagi.
Ini event streaming. Hook-nya wajib async function*, bentuk lain ga di-load. yield* next(e) nerusin semua potongan apa adanya, dan for await di atasnya bisa ngubah potongan satu-satu.
on("turn.step", async function* ($, e, next) {
const hasil = yield* next(e); // terusin semua potongan apa adanya
return hasil;
});tool.call jalan waktu engine mau jalanin tool: Bash, Edit, Read, MCP, semuanya.
Hook bisa blok pakai { deny }, ubah input-nya, atau await next(e) terus baca hasilnya. Saudaranya, tool.check, jalan waktu engine mutusin satu tool call boleh jalan atau ga.
on("tool.call", { tool: "Bash" }, async ($, e, next) => {
const ran = await next(e); // Bash beneran jalan di sini
const gagal = ran.deny === undefined && ran.isError === true;
$.ui.status(gagal ? `gagal: ${e.command.slice(0, 40)}` : undefined);
return ran;
});turn.complete jalan waktu turn selesai, pas durasinya dilaporin. Isi e di antaranya e.reason (misalnya "answer"), e.answer, dan e.turnId.
Tempat yang pas buat toast, nyimpen ringkasan, atau minta saran ke model.
on("turn.complete", async ($, e, next) => {
const result = await next(e);
if (e.reason === "answer") $.ui.toast(`Jawaban ${e.answer.length} karakter`);
return result;
});session.end jalan sekali waktu session selesai: exit, /clear, resume, logout, atau signal.
Setelah /clear, yang datang session.end dengan reason: "clear", dan ga ada session.start lagi sesudahnya.
on("session.end", async ($, e, next) => {
if (e.reason === "clear") $.ui.log("session di-/clear");
return next(e);
});Bagian Layar yang Bisa Diganti
Event ui.render bisa ngegambar ulang 15 component bawaan Claude Code. Gambar di bawah ini skematik, posisi persisnya bisa beda. Label di kanan tiap baris itu nama component-nya. Yang labelnya $.ui.status dan $.ui.toast diisi lewat method, dan kotak input sama sekali ga bisa digambar ulang. Klik baris mana aja buat lihat itu component apa dan mod mana yang udah ngutak-atiknya.
ToolUse: satu baris tool call di transcript. Props-nya tool, input, output, isRunning, isErrored, dan isInterrupted.
Mod tool-lines ngeganti baris ini total jadi ● tool_call: Bash(git status) - error, dengan warna titik dari statusnya.
ToolResult: blok hasil tool di bawah baris tool call.
tool-lines ngembaliin Box({}) yang kosong, jadi bloknya hilang. Transcript lengkap di Ctrl+O tetap nampilin semuanya.
ToolGroup: beberapa read, search, dan listing yang dilipet jadi satu baris hitungan.
tool-lines maksa isExpanded: true, jadi tiap call di dalamnya dibuka dan digambar lewat hook ToolUse.
UserMessage: prompt kamu yang muncul di transcript.
tool-lines membungkusnya pakai Box dengan marginBottom: 1, sekalian baca isExpanded buat tau Ctrl+O lagi kebuka atau ga.
AssistantMessage: jawaban teks dari model.
Mod glamour-dark dari repo yang sama nge-parse markdown-nya sendiri, terus gambar ulang pakai gaya glamour : heading berwarna, syntax highlight, dan tiap kalimat di baris sendiri.
InfoNotice: satu baris status redup di bawah logo, misalnya sumber model atau hint settings, biasanya dengan satu /command di ujung.
TurnDuration: baris penutup turn di transcript, misalnya Baked for 3s.
CommandOutput: output slash command di transcript, misalnya baris-baris /cost, atau text yang dibalikin hook command.run.
Kalau text-nya diubah, yang tampil di layar ikut berubah, tapi row yang disimpen tetap isi aslinya. Yang dibaca model tetap versi asli.
ToolProgress: baris progress di bawah tool yang lagi jalan. Untuk sekarang isinya pill run-in-background.
Bentuknya union berdasarkan kind, jadi match-nya pakai { props: { kind: "background_hint" } }.
Spinner: baris animasi selama turn jalan, misalnya Sauteing… (12s, 300 tokens).
word, message, dan suffix-nya bisa diubah, atau semuanya diganti tree sendiri.
AbovePrompt: band tepat di atas kotak input, tempat survey biasanya muncul. Bisa dilipet pakai ctrl+x ctrl+a.
Mod next-steps buatan Thariq Shihipar naruh tombol 1, 2, 3 di sini. Props yang sering dipakai: hasSurvey, isWorking, dan bodyColumns.
Kotak input ga ada di daftar 15 component itu, jadi ga bisa digambar ulang.
Yang masih bisa: isi draft pakai $.prompt.fill, kasih ghost text pakai $.prompt.suggest, dan baca ketikan lewat event prompt.edit dan prompt.autocomplete. Dialog izin tool juga ga ada di daftar, tapi keputusan izinnya bisa di-hook lewat tool.check.
PromptHint: baris hint di bawah input, kayak ? for shortcuts atau esc to interrupt. Hook bisa ganti hint-nya, nambah tail, atau gambar sendiri.
SessionMode: label mode redup di kanan footer, misalnya focus atau memory paused. Mod bisa nambah mode sendiri lewat props modes.
$.ui.status(teks) ngisi satu entry di status line dari hook mana aja. Ini method, bukan component. $.ui.status(undefined) buat ngosongin lagi.
$.ui.toast(teks) munculin notifikasi sementara, juga dari hook mana aja.
Pane: panel samping punya mod kamu. Buka pakai $.ui.open({ id, title }), terus gambar pakai ui.render dengan matcher { component: "Pane", requestId: id }.
Kalau dibuka karena kamu ngetik command atau mencet tombol, pane-nya langsung muncul. Kalau dibuka sendiri (dari session.start atau timer), dia baru muncul di terminal yang lebarnya minimal 144 kolom.
Satu yang ga kegambar di atas: AskUserQuestion, dialog pertanyaan pilihan ganda. Itu juga bisa di-hook.
Hook ui.render punya tiga cara ngegambar. return next(e) biarin tampilan bawaan. next({ ...e, props }) ngubah props-nya dulu. Atau balikin tree sendiri pakai elemen dari $.ui.resolve(e). Semua surface punya Box, Text, Button, Link, Code, dan Markdown. Terminal juga punya Raster (grid warna, cocok buat sparkline) dan Image.
e.surface bisa terminal, desktop, vscode, atau mobile. Mod tampilan biasanya ngecek ini dulu, karena tree yang bagus di terminal belum tentu cocok di desktop.
$: Remote ke Engine
Mod ga bisa nyentuh apa-apa secara langsung. Semuanya lewat $.<noun>.<method>(). Kolom terakhir nandain noun yang bisa nyentuh mesin atau internet kamu.
| Noun | Buat apa | Method | Akses ke luar |
|---|---|---|---|
$.ui | Gambar, kasih tau, buka pane | status, toast, open, close, log, invalidate | |
$.audio | Bunyi dari file milik mod, atau ngomong | play, speak | |
$.model | complete satu call tanpa history, fork pakai context session, classify pilih satu label | complete, fork, classify | kuota model |
$.tool | Bikin tool baru buat model, namanya jadi mcp__<plugin>__<name> | register, list, call | |
$.agent | Bikin tipe subagent baru, atau start satu | register, spawn, list | |
$.command | Slash command punya mod | register, list, run | |
$.mcp | Ngobrol sama MCP server | call, connect | |
$.session | Isi conversation, usage, model, folder | messages, usage, model, cwd, send | |
$.prompt | Isi kotak input, tanpa pernah ngirim sendiri | fill, suggest, read | |
$.turn | Hentiin turn yang lagi jalan | abort | |
$.config, $.settings | Row /config dan baca settings | config.set, settings.read | |
$.telemetry | Kirim record atau tandain pemakaian fitur | log, mark | |
$.state | Nilai selama session. Yang baca otomatis digambar ulang, dan nilainya selamat dari hot reload | get, set | |
$.store | Nilai yang awet lintas session | get, set, keys, delete | |
$.clock | Jam dan timer | now, after, every, sleep | |
$.fs | Baca dan tulis file | read, write, list, exists | disk |
$.process | Jalanin program di mesin kamu | run, spawn | shell |
$.http | Request ke mana aja | fetch | internet |
$.env | Environment variable, termasuk token | get, set | secret |
Detail yang menarik: panggilan $ juga lewat rantai yang sama kayak event. fs.read, http.fetch, dan process.run semuanya bisa di-hook. Artinya satu mod bisa nge-deny $.http.fetch punya mod lain.
Contoh Kasus
Delapan kasus, dari yang paling sering ditemuin. Dua yang pertama mod beneran yang ada di GitHub. Sisanya contoh resmi dari skill plugin-authoring bawaan Claude Code, atau dari doc di type declaration-nya.
Tiap tool call cukup satu baris. Event: ui.render di ToolUse, ToolResult, ToolGroup, dan UserMessage.
● tool_call: Read(src/app.ts)
● tool_call: Bash(git status)
● tool_call: Bash(npm test) - error
● tool_call: Edit(src/app.ts)Warna titiknya dari status: kuning kalau jalan atau nunggu, hijau kalau selesai, merah kalau error. Argumennya dipotong 35 karakter. Ini versi pendek dari Tickloop/claude-mods . Versi aslinya juga ngecek mode Ctrl+O biar tampilan lengkap tetap ada.
on("ui.render", { component: "ToolResult" }, async ($, e, next) => {
if (e.surface !== "terminal") return next(e);
const { Box } = $.ui.resolve(e);
return Box({}); // kosong: blok hasil ga digambar
});
on("ui.render", { component: "ToolUse" }, async ($, e, next) => {
if (e.surface !== "terminal") return next(e);
const { Box, Text } = $.ui.resolve(e);
const color = e.props.isErrored
? "error"
: e.props.isRunning || e.props.output === undefined
? "warning"
: "success";
return Box({
flexDirection: "row",
children: [
Text({ color, children: ["●"] }),
Text({ dimColor: true, children: ["tool_call: "] }),
Text({ bold: true, children: [e.props.tool] }),
],
});
});
// grup read/search dibuka, tiap isinya lewat hook ToolUse di atas
on("ui.render", { component: "ToolGroup" }, async ($, e, next) =>
next({ ...e, props: { ...e.props, isExpanded: true } }),
);Saran tiga prompt berikutnya. Alurnya turn.complete, lalu $.model.fork, lalu ui.render di AbovePrompt, lalu $.prompt.fill waktu tombolnya ditekan.
next:
1: jalanin test yang barusan ditulis
2: lakuin hal yang sama buat halaman settings
3: /code-review high
0: dismiss$.model.fork ngirim ulang request terakhir session (model, system prompt, tools, history) plus satu pesan tambahan. Prefix-nya kena prompt cache, jadi murah. Tapi call itu tetap jalan pakai model dan kuota session kamu.
Versi asli buatan Thariq Shihipar juga bersihin teks dari escape sequence, buang slash command yang ga ada, dan ngisi ghost text pakai $.prompt.suggest. Mod-nya ga pernah ngirim prompt sendiri.
let saran: string[] = [];
on("turn.start", async ($, e, next) => {
saran = [];
$.ui.invalidate("ui.render");
return next(e);
});
on("turn.complete", async ($, e, next) => {
const result = await next(e);
if (e.reason !== "answer") return result;
void (async () => {
// jalan di belakang, turn ga nunggu
const reply = await $.model.fork({
prompt:
"Jangan lanjutin task. Tebak 3 prompt berikutnya. Jawab JSON array of string.",
});
try {
saran = reply.isAnswered ? JSON.parse(reply.text) : [];
} catch {
saran = [];
}
$.ui.invalidate("ui.render");
})();
return result;
});
on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
const below = await next(e);
if (saran.length === 0 || e.props.isWorking) return below;
const { Box, Button } = $.ui.resolve(e);
return (
<Box flexDirection="column">
{below}
{saran.map((p, i) => (
<Button
key={`s${i}`}
hotkey={String(i + 1)}
plain
label={p}
onPress={() => $.prompt.fill({ text: p })}
/>
))}
</Box>
);
});Model ga boleh ngedit .env. Event: tool.call dengan matcher { tool: "Edit" }, plus .catch.
Model nerima alasan penolakannya, jadi dia bisa cari cara lain. .catch di bawah ini penting: tanpa itu, kalau hook-nya crash, hook di-skip dan Edit-nya tetap jalan.
const PROTECTED = /(^|\/)\.env(\.|$)/;
on("tool.call", { tool: "Edit" }, ($, e, next) =>
PROTECTED.test(e.file_path)
? { deny: `${$.plugin.name}: ${e.file_path} is protected here.` }
: next(e),
).catch(($, e, next) =>
next.called ? next(e) : { deny: `${$.plugin.name}: its guard failed.` },
);Kasih tau kalau turn kelamaan. Event: prompt.submit buat nyatet jam mulai, turn.complete buat ngitung, dan $.ui.toast buat ngasih tau.
Contoh resminya nyimpen hasilnya di $.state dan nampilin di band AbovePrompt. Variabel module kayak startedAt ke-reset tiap hot reload, jadi data yang digambar sebaiknya disimpen di $.state.
let startedAt = 0;
on("prompt.submit", async ($, e, next) => {
startedAt = await $.clock.now();
return next(e);
});
on("turn.complete", async ($, e, next) => {
const seconds = Math.round(((await $.clock.now()) - startedAt) / 1000);
if (seconds > 120) $.ui.toast(`Turn tadi ${seconds} detik`);
return next(e);
});Slash command yang buka panel. Alurnya session.start buat daftarin command, command.run buat jawab command-nya, $.ui.open buat buka pane, dan ui.render di Pane buat gambar isinya.
{ text } yang dibalikin command.run muncul di transcript sebagai CommandOutput.
on("session.start", async ($, e, next) => {
await $.command.register({
name: "catatan",
description: "Buka pane catatan",
});
return next(e);
});
on("command.run", { command: "catatan" }, async ($) => {
await $.ui.open({ id: "catatan", title: "Catatan" });
return { text: "Pane catatan dibuka." };
});
on("ui.render", { component: "Pane", requestId: "catatan" }, async ($, e) => {
const { Box, Text } = $.ui.resolve(e);
return (
<Box>
<Text>Isi pane di sini</Text>
</Box>
);
});Singkatan jadi prompt panjang. Event: prompt.submit dengan next({ ...e, text }).
Kamu ngetik tt, yang masuk ke model dan muncul di layar jadi “Jalanin semua test, terus benerin yang gagal”. Kebalikannya, return { drop: "alasan" } batalin prompt-nya.
const SINGKATAN: Record<string, string> = {
tt: "Jalanin semua test, terus benerin yang gagal",
cm: "Commit perubahan ini dengan message yang jelas",
};
on("prompt.submit", async ($, e, next) => {
const panjang = SINGKATAN[e.text.trim()];
return panjang ? next({ ...e, text: panjang }) : next(e);
});Tambah aturan ke system prompt. Event: prompt.compose, yang balikin { sections }.
Section baru taruh paling akhir dengan scope: "session". Kalau ada section shared sesudah section session, hook-nya di-skip. Event ini salah satu yang paling kuat: mod orang yang pakai prompt.compose bisa nyuruh model apa aja, jadi selalu baca isinya.
on("prompt.compose", async ($, e, next) => {
const { sections } = await next(e);
return {
sections: [
...sections,
{
id: "aturan-tim",
text: "Commit message selalu bahasa Inggris.",
scope: "session",
},
],
};
});Semua subagent pakai Haiku. Event: agent.spawn.
Selain ganti model, hook ini bisa balikin { model } sendiri, atau { deny: "alasan" } biar subagent-nya ga jalan. Kalau dipakai buat nolak, kasih .catch juga. Tanpa itu, hook yang crash di-skip dan subagent-nya tetap start.
on("agent.spawn", ($, e, next) => next({ ...e, model: "haiku" }));Daftar Lengkap 46 Event
Ini semua event yang bisa dipasang di on(...) di Claude Code 2.1.294. Ketik buat nyari, atau pilih satu grup.
tool.call Engine mau jalanin tool. Blok, ubah input, atau baca hasilnya. tool.check Engine lagi mutusin satu tool call boleh jalan atau ga. tool.describe Schema tool pertama kali dirender buat model. Deskripsi tool bisa diubah di sini. ui.render Engine mau gambar satu component. Ganti, bungkus, atau ubah props-nya. ui.resolve Waktu plugin load: tabel elemen per surface dan component. ui.press Button yang digambar mod ditekan. ui.input Input yang digambar mod berubah atau di-submit. ui.select Pilihan di Select yang digambar mod dipilih. ui.scroll Sebelum satu area layar ke-scroll. ui.focus Sebelum fokus pindah lewat Tab, panah, atau klik. ui.message Client punya mod ngirim pesan dari surface-nya. ui.fault Client punya mod gagal jalan di satu surface. prompt.submit Prompt di-Enter, sebelum turn mulai. Ubah teks atau batalin. prompt.fill Teks mau ditaruh di kotak input sebagai draft. prompt.suggest Teks mau jadi ghost text abu-abu yang bisa diambil pakai Tab. prompt.edit Kamu ngedit isi kotak input. prompt.autocomplete Kamu lagi ngetik, buat token di posisi kursor. prompt.compose Engine nyusun system prompt. Tambah, ganti, urutin, atau buang section. prompt.section Per section system prompt, satu-satu. prompt.context Sekali per conversation: context block di pesan pertama. prompt.attachment Tiap pesan yang engine selipin sendiri buat model, misalnya system-reminder. prompt.mention Tiap path yang kamu sebut pakai @. turn.start Turn mulai, sebelum model call pertama. turn.step Tiap request ke model dalam satu turn. Streaming, hook-nya async generator. turn.complete Turn selesai, pas durasinya dilaporin. session.start Sekali per process buat tiap plugin, sebelum prompt pertama. session.end Session selesai: exit, /clear, resume, logout, atau signal. session.append Tiap row yang disimpen conversation: prompt, jawaban, tool. session.compact Conversation mau di-compact, lewat /compact atau otomatis. session.send Pesan teks mau keluar dari conversation ini ke tempat lain. session.receive Kiriman masuk ke session: event relay atau pesan peer. session.attach Client remote gabung ke session. session.detach Client remote keluar, atau session-nya selesai. session.measure Engine ngukur ulang session dan ada angka yang berubah. command.run Slash command mau jalan. Mod bisa jawab command-nya sendiri. command.describe Tiap command waktu dilist di typeahead. config.set Satu row /config mau berubah. config.describe Tiap row /config waktu menu-nya dilist. agent.spawn Subagent mau di-start. Ganti model atau tolak. agent.offer Engine nawarin satu tipe agent ke model. Bisa disembunyiin. skill.prompt Engine nge-expand prompt satu skill buat model. attribution.text Engine nyusun teks git yang bakal ditulis model, misalnya attribution commit atau PR. telemetry.log Satu record mau dikirim ke collector OpenTelemetry kamu atau analytics Anthropic. telemetry.mark Satu pemakaian fitur ditandai. plugin.register Tiap hooks module mau gabung ke rantai, waktu load. engine.create Waktu $ lagi dibangun. Plugin bisa nambah noun baru ke $. Selain 46 itu masih ada dua keluarga lagi. Yang pertama classic.*, hook settings model lama kayak classic.Stop atau classic.SessionEnd, dengan e yang sama persis kayak yang mereka terima di stdin. Yang kedua semua panggilan $ kayak fs.read atau http.fetch, yang udah dibahas di bagian remote tadi.
Sebelum Install Mod Orang
Mod jalan di dalam Claude Code, dan lewat $ dia bisa baca file, jalanin program, dan akses internet. Lima hal ini cukup buat menilai satu mod dalam beberapa menit:
- Cari
$.fs,$.process,$.http, dan$.env. Empat noun ini yang bisa nyentuh disk, shell, internet, dan token kamu. Mod tampilan harusnya ga butuh satu pun. - Cari
tool.call,prompt.submit,prompt.compose, dansession.send. Ini yang bisa ngubah apa yang dikerjain atau dibaca model. - Cari
$.model.forkdancompletemakan kuota model kamu tiap kali dipanggil. - Jalanin
claude plugin validate <folder>. Dia ngelaporin semua yang di-hook dan dipanggil module itu, plus semua yang bakal ditolak engine. - Pin commit-nya. Kalau install-nya lewat clone, tiap
git pullberarti kode baru jalan di dalam Claude Code. Baca diff-nya dulu.
Sebagai contoh, ini hasilnya buat tiga mod yang saya bahas di atas:
| Mod | Event | Panggilan $ | Akses ke luar |
|---|---|---|---|
tool-lines | ui.render (4 hook) | ui.resolve, ui.invalidate | ga ada |
glamour-dark | ui.render (1 hook) | ui.resolve | ga ada |
next-steps | turn.start, turn.complete, ui.render | model.fork, command.list, prompt.fill, prompt.suggest | satu call model per turn |
Kalau mau nyari inspirasi, katalog awesome-claude-code-mods nge-scan GitHub dan nyatet akses tiap mod pakai validator yang sama. Mod buatan Anthropic sendiri (diff, agents-md, sec-default, telemetry) ada di anthropics/claude-code , dan itu contoh kode yang paling bisa dipercaya.
Bikin Mod Pertama
1. Minta Claude Bikinin
Cara paling cepat: bilang “bikin mod yang …” di session Claude Code. Skill plugin-authoring bawaannya nulis tiga file tadi di ~/.claude/dev-mods/<session>/<mod>/, terus nanya “Enable hot reloading for this session?”. Kalau dijawab enable, mod-nya langsung jalan begitu turn selesai.
2. Atau Jalanin Folder Sendiri
claude --plugin-dir ./mod-sayaFolder-nya di-watch. Tiap kali file di-save, module-nya di-load ulang. Variabel module ke-reset, sedangkan $.state dan $.store tetap.
3. Cek Sebelum Dipakai
claude plugin validate ./mod-saya
claude plugin test ./mod-saya # jalanin *.test.ts4. Kalau Mod-nya Diam Aja
Jalanin claude --debug. Hook yang gagal dan tree yang ditolak tercatat di debug log. Selama hot reload, transcript juga nampilin satu baris redup kayak mod-saya: ui.render (ToolUse) refused: …; the engine drew its own.
5. Share
Taruh .claude-plugin/marketplace.json di repo-nya. Orang lain cukup install pakai satu baris:
/plugin install mod-saya --marketplace <owner>/<repo>Kesimpulan
- Mod itu satu file TypeScript yang nyelip di rantai hook. Setiap event lewat semua mod dulu, baru sampai ke perilaku bawaan engine.
- Rumusnya cuma
on(event, matcher, hook), dan hook-nya cuma bisa empat hal: terusin, ubah dulu, jawab sendiri, atau baca hasilnya. ui.renderbisa ngegambar ulang 15 component bawaan. Kotak input dan dialog izin ga termasuk.- Semua akses ke luar lewat
$, dan$.fs,$.process,$.http, serta$.envitu yang perlu dicek dulu sebelum install mod orang. - Hook yang error di-skip. Buat mod penjaga, selalu pasang
.catch.
Saya sendiri masih di tahap ngoprek. Kalau kamu udah bikin mod yang seru, kasih tau saya, siapa tau bisa masuk post berikutnya 🚀
Satu rasi dengan tulisan ini
Tulisan lain dengan tag yang sama.
- Olympus: Terminal yang Bisa Disetir dari CodeTag sama: AI Agents, Claude Code, Developer Tools, Deep Dive
- Kalender Libur Nasional Sekarang Bisa Diajak Ngobrol: Bikin MCP Server buat CalendarTag sama: TypeScript, AI Agents
- Kenapa Saya Memilih DESIGN.md daripada Claude Design (at least for now)Tag sama: Claude Code, TypeScript