> ## Documentation Index
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Buat sebuah mod

> Biarkan Claude menulis mod Claude Code dari deskripsi, atau tulis sendiri yang menghitung panggilan alat dan menambahkan perintah. Pelajari loop reload dan validate.

Mod adalah [plugin](/docs/id/plugins/overview) Claude Code dengan file entry, disebut hooks module: file JavaScript atau TypeScript yang fungsi-fungsinya dipanggil Claude Code ketika peristiwa terjadi. Ada dua cara untuk membuatnya:

* **Minta Claude untuk menulisnya**: [jelaskan apa yang Anda inginkan](#ask-claude-for-a-mod) dalam sesi Claude Code
* **Tulis sendiri**: [ikuti tutorial](#write-a-mod-yourself) untuk mempelajari cara kerja kode mod. Anda tidak memerlukan Node.js, bundler, atau build step, karena Claude Code memuat file `.js` dan `.ts` secara langsung.

Jika Anda belum memutuskan apakah mod adalah alat yang tepat, baca [perbandingan di overview](/docs/id/plugins/mods/overview#compare-mods-settings-hooks-skills-and-mcp-servers) terlebih dahulu.

<Note>
  Mod memerlukan Claude Code v2.1.287 atau lebih baru. Di shell Anda, jalankan `claude --version` untuk memeriksa. Untuk melihat apakah mod dapat dimuat untuk Anda, lihat [Periksa apakah mod dapat dimuat](/docs/id/plugins/mods/troubleshoot#check-whether-mods-can-load).
</Note>

<h2 id="ask-claude-for-a-mod">
  Minta Claude untuk sebuah mod
</h2>

Jelaskan mod yang Anda inginkan dalam sesi Claude Code interaktif, dan Claude menulisnya. Claude bekerja dari [skill](/docs/id/skills) bawaan bernama `plugin-authoring`, yang memberitahu di mana menulis mod, peristiwa dan metode apa yang dimiliki versi Anda, dan bagaimana mod dimuat. Claude dapat memuat skill ketika Anda meminta mod, atau Anda dapat memuatnya sendiri dengan menjalankan `/plugin-authoring` di prompt Claude Code.

Mod berjalan setelah Anda menyetujuinya, kecuali dalam [sesi di mana mod yang ditulis Claude tidak dapat dimuat](#sessions-that-skip-the-approval).

<Steps>
  <Step title="Jelaskan mod">
    Minta mod dengan kata-kata Anda sendiri, misalnya `buat mod yang menunjukkan cabang git saat ini di atas prompt`. Claude menulis mod dalam direktori miliknya sendiri di folder mods sesi, yaitu `~/.claude/dev-mods/` diikuti dengan ID sesi. Jalur lengkap mod terlihat seperti `~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/`.

    <Note>
      Dalam [mode izin](/docs/id/permission-modes#protected-paths) `default` dan `acceptEdits`, Claude Code menanyakan sebelum Claude membuat setiap file mod, karena `~/.claude` adalah jalur yang dilindungi. Setujui setiap file saat muncul.
    </Note>
  </Step>

  <Step title="Setujui mod">
    Ketika Claude menyimpan file pertama, Claude Code menanyakan apakah akan mengaktifkan hot reloading untuk sesi. Hot reloading menjalankan mod yang ditulis Claude dalam sesi ini dan mengambil setiap perubahan kemudian.

    Pilih salah satu jawaban ini:

    * **Aktifkan untuk sesi ini**: mod di folder mods sesi dimuat ketika giliran berakhir, dan dimuat ulang di akhir setiap giliran yang mengubahnya. Jawaban Anda berlaku untuk sesi, termasuk setelah Anda melanjutkannya.
    * **Tidak sekarang**: tidak ada yang dimuat untuk sekarang. File tetap di tempat Claude menulisnya, dan mod dimuat saat sesi itu dimulai berikutnya. Untuk mencegah mod dimuat selamanya, hapus direktorinya.
  </Step>

  <Step title="Periksa bahwa mod dimuat">
    Jalankan `/plugin` di prompt Claude Code dan tekan Tab sampai tab **Installed** dipilih. Ini mencantumkan mod, dan Anda dapat mematikannya di sana.
  </Step>

  <Step title="Coba mod">
    Gunakan apa yang Anda minta. Untuk prompt contoh, nama cabang saat ini muncul di atas kotak prompt. Jika mod tidak melakukan apa yang Anda inginkan, beri tahu Claude apa yang harus diubah. Mod dimuat ulang di akhir setiap giliran yang mengubah filenya, sehingga Anda dapat mencoba perubahan segera setelah Claude selesai.
  </Step>
</Steps>

<h3 id="use-the-mod-in-other-sessions">
  Gunakan mod di sesi lain
</h3>

Mod yang ditulis Claude hanya dimuat dalam sesi yang membuatnya, dan Claude Code menghapus folder mods sesi itu setelah lebih lama dari [`cleanupPeriodDays`](/docs/id/settings-reference#cleanupperioddays). Untuk menyimpan mod, salin direktorinya keluar dari folder mods ke tempat Anda sendiri, seperti `~/mods/git-branch`. Kemudian pilih cara memuatnya:

* **Dalam sesi yang Anda mulai**: di shell Anda, jalankan `claude --plugin-dir ~/mods/git-branch`
* **Untuk orang lain**: [tambahkan ke marketplace](#share-your-mod) sehingga mereka dapat menginstalnya

<h3 id="sessions-that-skip-the-approval">
  Sesi di mana mod yang ditulis Claude tidak dapat dimuat
</h3>

Mod yang ditulis Claude hanya dimuat setelah Anda menyetujuinya, dalam workspace terpercaya di mana mod diizinkan untuk berjalan. Dalam sesi ini tidak dimuat:

* **Tidak ada yang ada untuk menyetujui**: sesi tidak dapat menampilkan prompt kepada Anda, seperti dalam run `claude -p` atau [mode `dontAsk`](/docs/id/permission-modes)
* **Workspace tidak terpercaya**: Anda belum menerima prompt kepercayaan untuk direktori
* **Mod dihentikan**: Anda memulai dengan `--safe-mode` atau `--bare`, Anda menetapkan `disableAllHooks`, atau [pengaturan terkelola](/docs/id/plugins/mods/admin#choose-how-much-to-allow) organisasi Anda memblokir

<h2 id="write-a-mod-yourself">
  Tulis mod sendiri
</h2>

Dalam tutorial ini Anda membangun mod bernama `first-mod` yang menghitung panggilan alat yang dibuat Claude, menampilkan hitungan di samping spinner saat Claude bekerja, dan menambahkan perintah `/tally` yang mencetaknya. Anda kemudian membaca deklarasi tipe yang ditulis Claude Code di samping mod Anda dan menjalankan `claude plugin validate`. Bersama-sama mereka menunjukkan kepada Anda peristiwa dan metode yang ditawarkan versi Anda dan apa yang dibaca Claude Code dari kode Anda.

Rekaman ini menunjukkan mod yang selesai. Spinner menghitung panggilan alat, `/tally` mencetak hitungan, dan edit ke kode berlaku saat sesi berjalan:

<Frame>
  <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-first-mod-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=eb561134afa90375777408453ba51c77" aria-label="Dalam sesi Claude Code, prompt 'list the files here and read the README' diketik dan dikirim. Spinner membaca 'Thinking · tool calls: 1' dan hitungan naik saat Claude bekerja. Perintah /tally mencetak 'first-mod: Claude has made 3 tool calls since this mod loaded'. Sebuah baris mengatakan first-mod dimuat ulang dan mencantumkan empat hooks-nya. Pada prompt berikutnya spinner membaca 'Thinking · tools used: 1'." data-path="images/mods-first-mod-light.mp4" />

  <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-first-mod-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=09779dadc7ef66c2b1e2da0c2e31ac72" aria-label="Dalam sesi Claude Code, prompt 'list the files here and read the README' diketik dan dikirim. Spinner membaca 'Thinking · tool calls: 1' dan hitungan naik saat Claude bekerja. Perintah /tally mencetak 'first-mod: Claude has made 3 tool calls since this mod loaded'. Sebuah baris mengatakan first-mod dimuat ulang dan mencantumkan empat hooks-nya. Pada prompt berikutnya spinner membaca 'Thinking · tools used: 1'." data-path="images/mods-first-mod-dark.mp4" />
</Frame>

Anda menulis tiga file:

```text theme={null}
first-mod/
├── .claude-plugin/
│   └── plugin.json
└── hooks/
    ├── hooks.json
    └── register.js
```

* **`plugin.json`**: [manifest](/docs/id/plugins/manifest-reference) plugin
* **`hooks.json`**: [menunjuk ke file kode Anda](/docs/id/plugins/mods/reference#files)
* **`register.js`**: kode Anda, disebut hooks module

<Steps>
  <Step title="Buat direktori plugin">
    Buat dua direktori yang menyimpan file:

    <Tabs>
      <Tab title="Bash atau Zsh">
        ```bash theme={null}
        mkdir -p first-mod/.claude-plugin first-mod/hooks
        ```
      </Tab>

      <Tab title="PowerShell">
        ```powershell theme={null}
        New-Item -ItemType Directory -Force first-mod\.claude-plugin, first-mod\hooks
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Tulis manifest">
    Mod adalah plugin, dan mod memerlukan [manifest](/docs/id/plugins/manifest-reference). Manifest mod ini tidak memiliki field khusus. Simpan ini sebagai `first-mod/.claude-plugin/plugin.json`:

    ```json first-mod/.claude-plugin/plugin.json theme={null}
    {
      "name": "first-mod",
      "version": "0.1.0",
      "description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command",
      "author": { "name": "Your Name" }
    }
    ```
  </Step>

  <Step title="Beri tahu Claude Code di mana kode Anda berada">
    Ketika Claude Code memuat plugin, ia membaca `hooks/hooks.json` plugin. Kunci `modules` dalam file itu memberikan jalur ke kode Anda, dan memilikinya adalah apa yang membuat plugin menjadi mod. Cantumkan satu jalur, relatif terhadap `hooks.json`. Di sini menunjuk ke `register.js`, yang Anda tulis di langkah berikutnya.

    Simpan ini sebagai `first-mod/hooks/hooks.json`:

    ```json first-mod/hooks/hooks.json theme={null}
    {
      "description": "The first-mod hooks module",
      "modules": ["./register.js"]
    }
    ```
  </Step>

  <Step title="Tulis kode">
    File ini adalah kode mod, disebut hooks module. Ketika mod dimuat, Claude Code memanggil fungsi `register` yang diekspor file dan meneruskan fungsi bernama [`on`](/docs/id/plugins/mods/reference#the-hook-function). Setiap panggilan ke `on` mendaftarkan event handler, disebut hook, untuk peristiwa yang dinamainya.

    Simpan ini sebagai `first-mod/hooks/register.js`:

    ```javascript first-mod/hooks/register.js theme={null}
    // The count, shared by the hooks below
    let calls = 0

    // Claude Code calls this once when the mod loads
    export function register(on) {
      // Runs when the session starts, before your first prompt
      on('session.start', async ($, e, next) => {
        // Add the /tally command
        await $.command.register({
          name: 'tally',
          description: 'Show how many tool calls Claude has made',
        })
        // Let the session start as usual
        return next(e)
      })

      // Runs each time Claude is about to use a tool
      on('tool.call', async ($, e, next) => {
        calls += 1
        // Ask Claude Code to draw the interface again, so the new count shows
        $.ui.invalidate('ui.render')
        // Let the tool run as usual
        return next(e)
      })

      // Runs when you type /tally, and only then, because of the matcher
      on('command.run', { command: 'tally' }, async () => {
        // The text to print in the transcript
        return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
      })

      // Runs each time Claude Code draws the spinner
      on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
        // Keep Claude Code's spinner, with the count added after its word
        return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
      })
    }
    ```

    File menyimpan hitungan dalam `calls` dan mendaftarkan empat hooks:

    * **[`session.start`](/docs/id/plugins/mods/reference#session)** berjalan ketika sesi dimulai, sebelum prompt pertama Anda, dan lagi setiap kali mod dimuat ulang. Ini menambahkan perintah `/tally` ke Claude Code.
    * **[`tool.call`](/docs/id/plugins/mods/reference#tools)** berjalan setiap kali Claude akan menggunakan alat. Ini menambah satu ke `calls` dan meminta Claude Code untuk menggambar antarmuka lagi.
    * **[`command.run`](/docs/id/plugins/mods/reference#commands-and-configuration)** berjalan ketika Anda mengetik `/tally`. Ini mengembalikan teks untuk dicetak.
    * **[`ui.render`](/docs/id/plugins/mods/reference#interface)** berjalan setiap kali Claude Code menggambar spinner. Ini menambahkan hitungan setelah kata spinner.

    [Cara kerja mod contoh](#how-the-example-mod-works) menjelaskan tiga argumen yang diterima setiap hook dan apa yang dikembalikan masing-masing.
  </Step>

  <Step title="Muat mod">
    Mulai Claude Code dengan flag `--plugin-dir`, yang memuat direktori plugin untuk satu sesi tanpa menginstalnya:

    ```bash theme={null}
    claude --plugin-dir ./first-mod
    ```
  </Step>

  <Step title="Coba mod">
    Minta Claude untuk melakukan sesuatu yang memerlukan beberapa panggilan alat, seperti `list the files here and read the README`. Saat Claude bekerja, kata spinner diikuti oleh hitungan yang naik, seperti `Thinking · tool calls: 2…`. Ketika Claude selesai, ketik `/tally` dan tekan Enter. Transkrip menunjukkan `first-mod: Claude has made 2 tool calls since this mod loaded`, dengan hitungan Anda sendiri. Claude Code menempatkan nama plugin di depan teks perintah.

    Untuk memeriksa perintah tanpa sesi interaktif, jalankan dalam mode non-interaktif:

    ```bash theme={null}
    claude -p "/tally" --plugin-dir ./first-mod
    ```

    ```text theme={null}
    first-mod: Claude has made 0 tool calls since this mod loaded
    ```

    Jika `/tally` tidak ada dalam daftar perintah, modul tidak dimuat. Lihat [Cari tahu mengapa mod tidak melakukan apa pun](/docs/id/plugins/mods/troubleshoot#find-out-why-a-mod-does-nothing).
  </Step>

  <Step title="Ubah kode saat sesi berjalan">
    Biarkan sesi tetap terbuka. Dalam `register.js`, ubah `' · tool calls: '` menjadi `' · tools used: '` dalam hook `ui.render` dan simpan. Baris yang disorot adalah yang berubah:

    ```javascript first-mod/hooks/register.js {4} theme={null}
      // Runs each time Claude Code draws the spinner
      on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
        // Keep Claude Code's spinner, with the count added after its word
        return next({ ...e, props: { ...e.props, suffix: ' · tools used: ' + calls + '…' } })
      })
    ```

    Sebuah baris dalam transkrip mengatakan `first-mod` dimuat ulang dan mencantumkan hooks-nya, dan spinner berikutnya menggunakan teks baru, seperti `Thinking · tools used: 1…`.
  </Step>
</Steps>

<h3 id="how-the-example-mod-works">
  Cara kerja mod contoh
</h3>

Setiap fungsi yang Anda teruskan ke `on` adalah hook, yang merupakan event handler. Claude Code meneruskan setiap hook dengan tiga argumen yang sama:

* **Mods API**, bernama `$`: setiap metode yang dapat dipanggil mod untuk menjangkau di luar dirinya, dalam [namespaces](/docs/id/plugins/mods/reference#mods-api-methods) seperti `$.ui` dan `$.command`
* **Event**, bernama `e`: [input event](/docs/id/plugins/mods/reference#events) sebagai data biasa, seperti nama dan argumen panggilan alat
* **Next handler**, bernama [`next`](/docs/id/plugins/mods/events#how-a-hook-handles-an-event): fungsi yang meneruskan event ke mod lain dan kemudian ke perilaku Claude Code sendiri, dan mengembalikan hasilnya

Hook dalam `first-mod` menangani peristiwa mereka dalam tiga cara hook dapat:

* **Observe**: hook `session.start` mendaftarkan perintah, dan hook `tool.call` menghitung panggilan dan meminta redraw. Keduanya mengembalikan `next(e)`, sehingga sesi dimulai dan alat berjalan seperti biasa.
* **Answer**: hook `command.run` mengembalikan hasilnya sendiri dan tidak pernah memanggil `next`. Argumen kedua ke `on`, `{ command: 'tally' }`, adalah filter, disebut [matcher](/docs/id/plugins/mods/events#filter-which-events-a-hook-handles), sehingga hook hanya berjalan untuk `/tally`.
* **Rewrite**: hook `ui.render` memanggil `next` dengan salinan `e` yang `suffix`-nya menyimpan hitungan, sehingga Claude Code menggambar spinner biasanya dengan teks Anda setelah kata

Claude Code mengawasi direktori yang dimuat dengan `--plugin-dir` dan hot-reload hooks module ketika file di dalamnya berubah. Setiap reload menjalankan `register` lagi, sehingga `calls` kembali ke `0` dan `/tally` mulai menghitung lagi. Untuk menyimpan nilai di seluruh reload, lihat [Simpan state](/docs/id/plugins/mods/interface#keep-state).

<h2 id="keep-working-on-a-mod">
  Terus bekerja pada mod
</h2>

Setelah mod dimuat, Anda dapat membuat Claude mengubahnya, memeriksa kode Anda terhadap definisi tipe untuk versi Anda, mencantumkan peristiwa dan panggilan yang ditemukan Claude Code di dalamnya, dan mengujinya.

<h3 id="change-a-mod-with-claude">
  Ubah mod dengan Claude
</h3>

Untuk mengubah mod yang sudah Anda miliki, mulai sesi dengan `--plugin-dir` menunjuk ke direktori mod, sehingga apa yang ditulis Claude dimuat dalam sesi yang sama:

```bash theme={null}
claude --plugin-dir ./first-mod
```

Kemudian minta perubahan, misalnya `add a /tally-reset command to this mod that sets the tally back to zero`. Claude mengedit hooks module, menjalankan `claude plugin validate`, dan memperbaiki apa yang dilaporkannya. Direktori yang Anda muat dengan `--plugin-dir` adalah [jalur yang dilindungi](/docs/id/permission-modes#protected-paths), sehingga dalam mode `default` dan `acceptEdits` Anda diminta untuk menyetujui setiap edit Claude ke mod. Tabel jalur yang dilindungi memberikan hasil untuk mode izin lainnya.

File yang disimpan Claude selama gilirannya dimuat ulang ketika giliran berakhir, sehingga Anda dapat mencoba `/tally-reset` segera setelah Claude selesai.

<h3 id="get-the-types-for-your-build">
  Dapatkan definisi tipe untuk versi Anda
</h3>

Setiap kali Claude Code memuat atau memuat ulang mod dari direktori yang Anda teruskan ke `--plugin-dir`, atau mod [yang ditulis Claude untuk Anda](#ask-claude-for-a-mod), ia menulis file deklarasi TypeScript, berakhir dengan `.d.ts`, ke dalam `.claude-plugin/types/` di dalam direktori mod. Mereka menjelaskan peristiwa yang tepat, metode mods API, dan elemen dalam versi Claude Code yang Anda jalankan, sehingga editor Anda dapat autocomplete dan type-check hooks Anda. Untuk menelusuri deklarasi secara online, baca [`mods/types/claude-code.d.ts`](https://github.com/anthropics/claude-code/blob/main/mods/types/claude-code.d.ts) di repositori Claude Code, yang baris pertamanya menamai versi yang menulisnya. Direktori menyimpan file-file ini:

| Path | Apa yang dideklarasikan |
| :- | :- |
| `claude-code/index.d.ts` | Setiap event dan input serta hasilnya, setiap namespace dan metode mods API, dan elemen yang dapat digambar setiap surface |
| `claude-code-tools/index.d.ts` | Input dan hasil alat bawaan, sehingga memeriksa `e.tool === 'Bash'` mempersempit `e` |
| `claude-code-mcp/index.d.ts` | Input alat MCP yang terhubung terakhir kali Anda menyimpan file dalam mod |
| `index.d.ts` dalam direktori bernama untuk plugin | Apa yang ditambahkan plugin itu ke mods API. Ada satu direktori untuk setiap plugin yang dicantumkan `plugin.json` Anda di bawah `dependencies`. |
| `tsconfig.json` | Opsi compiler yang sesuai untuk hooks module |

Jika mod Anda tidak memiliki `tsconfig.json` miliknya sendiri, Claude Code menambahkan satu di root mod yang memperluas yang dihasilkan, sehingga editor Anda dan `tsc -p ./first-mod` type-check mod tanpa setup lebih lanjut.

Peristiwa dan metode dapat berubah antar rilis, jadi percayai file-file ini daripada halaman apa pun, termasuk yang ini, ketika mereka tidak setuju.

`claude-code/index.d.ts` adalah referensi paling lengkap untuk build Anda, dengan komentar dan contoh untuk setiap metode mods API. Untuk mencari sesuatu, cari file untuk namanya, seperti `'tool.call'`.

<h3 id="check-what-claude-code-reads-from-your-mod">
  Periksa apa yang dibaca Claude Code dari mod Anda
</h3>

Untuk melihat mod Anda seperti yang dilihat Claude Code, tanpa menjalankan kode atau memulai sesi, gunakan `claude plugin validate`. Ini memeriksa manifest dan menjalankan analisis statis yang sama pada sumber hooks module yang dijalankan Claude Code saat memuat mod. Di shell Anda, jalankan pada direktori mod:

```bash theme={null}
claude plugin validate ./first-mod
```

Untuk `first-mod`, output mencakup baris-baris ini.

```text theme={null}
  ❯ ./register.js hooks: session.start, tool.call, command.run{command=tally}, ui.render{component=Spinner}
  ❯ ./register.js calls: $.command.register, $.ui.invalidate

✔ Validation passed
```

Baris `hooks:` mencantumkan peristiwa yang dihubungkan modul Anda, masing-masing dengan filternya dalam kurung kurawal. Baris `calls:` mencantumkan setiap metode mods API yang dipanggilnya. Modul yang membaca atau menetapkan variabel lingkungan juga mendapat baris `env reads:` dan `env writes:`, dan yang menggunakan [`$.state`](/docs/id/plugins/mods/interface#keep-state) mendapat `state reads:` dan `state writes:`.

Jika event yang dimaksudkan untuk dihubungkan hilang dari baris pertama, Claude Code juga tidak akan memanggil hook itu. Penyebab biasanya adalah nama event yang salah eja, yang dilaporkan perintah sebagai kesalahan seperti `"tool.calls" is not an event`.

Ikuti aturan-aturan ini sehingga analisis statis dapat menemukan setiap hook dan panggilan:

* Eja setiap panggilan mods API secara lengkap: `$`, namespace, kemudian metode, seperti dalam `$.store.get('notes')`. Anda dapat meneruskan `$` ke fungsi yang dideklarasikan di tingkat atas file yang sama, dan untuk fungsi Anda bernama `loadNotes`, baris `calls:` kemudian membaca `$.store.get (via loadNotes)`. Meneruskan `$` ke metode, fungsi yang didefinisikan di dalam hook, atau fungsi yang Anda impor dari file lain Anda gagal validasi. Fungsi `read` dan `update` yang digunakan [`$.state`](/docs/id/plugins/mods/interface#keep-state) adalah impor yang dapat mengambilnya. Jangan menetapkan `$` atau salah satu namespace-nya ke variabel, destructure, atau indexnya dengan nama yang dihitung. `const ui = $.ui` gagal dengan `$.ui is used as a value`.
* Tulis nama event dalam setiap panggilan `on` sebagai string literal, seperti `'tool.call'`. Variabel, atau loop atas daftar nama, gagal dengan `the event name passed to on() is not a string literal`.
* Di dalam `register`, jangan deklarasikan variabel atau parameter kedua bernama `on`. Validasi gagal dengan `"on" is declared again (shadowed)`.
* Impor hanya dari file di dalam direktori plugin, dengan jalur relatif. Satu-satunya impor bare yang diizinkan adalah `claude-code`, untuk tipe dan beberapa helper.
* Gunakan deklarasi `import` di atas file, seperti dalam `import { name } from './file.js'`. `import()` dinamis gagal dengan `a dynamic import(); a hooks module imports its own files with an import declaration`.
* Tulis setiap file sebagai ES module, dengan `import` dan bukan `require`. [Reference](/docs/id/plugins/mods/reference#files) mencantumkan ekstensi file yang dimuat Claude Code.

<h3 id="test-the-mod">
  Uji mod
</h3>

Anda dapat menulis tes otomatis untuk mod dan menjalankannya dari shell Anda dengan `claude plugin test`, tanpa sesi, sign-in, atau jaringan. Tes menaikkan peristiwa yang ditangani hooks Anda dan memeriksa apa yang dilakukan hooks.

Tes ini menaikkan dua panggilan alat, menjalankan `/tally`, dan memeriksa bahwa balasan menghitung keduanya. Simpan sebagai `first-mod/tests/first-mod.test.ts`:

```typescript first-mod/tests/first-mod.test.ts theme={null}
import { expect, test } from 'claude-code/testing'

test('/tally reports the tool calls the mod has seen', async ($, on) => {
  // Answer each tool call in Claude Code's place, so no tool runs
  on('tool.call', () => ({ result: 'ok' }))

  // Raise two tool calls, which the mod's tool.call hook counts
  await $.tool.call({ tool: 'Bash', command: 'ls' })
  await $.tool.call({ tool: 'Read', file_path: 'README.md' })

  // Run /tally and check the text its hook returns
  const answer = await $.command.run({ command: 'tally', args: '' })
  expect(answer.text).toBe('Claude has made 2 tool calls since this mod loaded')
})
```

Di shell Anda, jalankan tes dari direktori `first-mod`:

```bash theme={null}
claude plugin test
```

Output menamai setiap tes dan apakah itu lulus, dengan waktu yang bervariasi dari run ke run:

```text theme={null}
tests/first-mod.test.ts:
(pass) /tally reports the tool calls the mod has seen [22.87ms]

 1 pass
 0 fail
Ran 1 test across 1 file. [0.19s]
```

[Uji mod](/docs/id/plugins/mods/test) mencakup stubbing panggilan model atau store, dan menguji timer dan gambar.

<h2 id="share-your-mod">
  Bagikan mod Anda
</h2>

Mod adalah plugin, jadi Anda memversinya dalam manifest dan orang menginstal dan memperbarui dengan perintah `/plugin`. Untuk memberikannya kepada orang lain, [tambahkan ke marketplace](/docs/id/plugins/publish).

Sebelum Anda melakukannya, periksa `name` plugin: `claude plugin validate` gagal nama yang [terlihat seperti salah satu milik Anthropic sendiri](/docs/id/plugins/manifest-reference#name), seperti yang dimulai dengan `claude-`. Peristiwa dan metode dapat berubah antar rilis, jadi README Anda adalah tempat untuk mengatakan versi Claude Code mana yang Anda uji.

Terus berkembang terhadap direktori dengan `--plugin-dir`, bukan terhadap salinan yang diinstal. Claude Code cache plugin yang diinstal berdasarkan versi, sehingga edit Anda tidak mencapai salinan yang diinstal sampai Anda menaikkan versi dan menginstal lagi.

<h2 id="next-steps">
  Langkah berikutnya
</h2>

* [Gambar di antarmuka](/docs/id/plugins/mods/interface): buka pane, gambar di atas prompt, dan tambahkan tombol dan field teks
* [Bereaksi terhadap peristiwa](/docs/id/plugins/mods/events): hook panggilan alat, prompt, dan giliran
* [Gunakan mods API](/docs/id/plugins/mods/api): tambahkan perintah dan alat, panggil model, dan jalankan pekerjaan pada timer
* [Uji mod](/docs/id/plugins/mods/test): stub apa yang akan dijawab Claude Code, dan uji timer dan gambar
* [Troubleshoot mod](/docs/id/plugins/mods/troubleshoot): alasan mod tidak melakukan apa pun, dan debug log
* [Baca sumber mod bawaan](/docs/id/plugins/mods/overview#read-the-source-of-built-in-mods): plugin lengkap, masing-masing dengan hooks module dan tesnya
