SpyBara
Go Premium

plugins/mods/api.md 2026-09-30 23:00 UTC to 2026-10-01 21:02 UTC

This page contains 218 additions and 0 deletions.

2026
Thu 1 21:02

Verwenden Sie die mods API

Rufen Sie die mods API aus einem Claude Code Mod auf, um Befehle und Tools hinzuzufügen, ein Modell aufzurufen, Arbeiten auf einem Timer auszuführen, Nachrichten an andere Sitzungen zu senden und auf Dateien und das Netzwerk zuzugreifen.

Die mods API ist die Menge von Methoden, die ein Mod aufruft, um zu handeln: Befehle und Tools hinzufügen, ein Modell aufrufen, Arbeiten zwischen Ereignissen ausführen und auf das Dateisystem, Prozesse und das Netzwerk zugreifen. Jeder Hook erhält sie als sein erstes Argument, $, mit den Methoden in Namespaces wie $.ui und $.fs gruppiert. Ereignisse entscheiden, wann ein Hook ausgeführt wird, und die mods API ist das, was der Hook aufruft, sobald er dies tut.

Erstellen Sie Ihren ersten Mod, bevor Sie hier beginnen. Für jede Methode siehe mods API Methoden oder lesen Sie die Typen für Ihren Build.

Fügen Sie einen Befehl oder ein Tool hinzu

Ein Mod kann einen Befehl hinzufügen, den der Benutzer ausführen kann, und ein Tool, das Claude aufrufen kann. Registrieren Sie beide in einem session.start Hook. Claude Code wartet auf diesen Hook vor der ersten Eingabeaufforderung, sodass das, was Sie registrieren, ab der ersten Runde verfügbar ist.

Fügen Sie einen Befehl hinzu

Ein Befehl ist für den Benutzer. Registrieren Sie ihn und behandeln Sie dann command.run für seinen Namen. Dieses Beispiel fügt einen /standup Befehl hinzu, der eine optionale Anzahl von Tagen akzeptiert:

on('session.start', async ($, e, next) => {
  // Add /standup to the command list, with the description the user sees there
  await $.command.register({ name: 'standup', description: 'Summarize what changed today', argumentHint: '[days]' })
  return next(e)
})

// The matcher limits the hook to /standup, so other commands don't reach it
on('command.run', { command: 'standup' }, async ($, e) => {
  // e.args is the text typed after the command name, or an empty string
  return { text: 'Summary for the last ' + (e.args || '1') + ' day(s): ...' }
})

Nach dem Start der Sitzung erscheint /standup mit seiner Beschreibung in der Liste, die Sie sehen, wenn Sie / eingeben. Der argumentHint wird in der Eingabeaufforderung angezeigt, nachdem Sie den Befehl und ein Leerzeichen eingeben, wie in /standup [days]. Wenn Sie /standup 3 ausführen, gibt der zweite Hook Summary for the last 3 day(s): ... zurück, und das Transkript zeigt diesen Text nach dem Namen des Plugins. Der Hook ruft niemals next auf, da der Befehl kein anderes Verhalten als Ihres hat.

Der text, den Sie zurückgeben, wird im Transkript gedruckt und Claude liest ihn. Um nichts zu drucken, wie ein Befehl, der nur einen Bereich öffnet, geben Sie {} zurück. Um den Befehl auszuführen, während Claude arbeitet, fügen Sie immediate: true zur Registrierung hinzu.

Wählen Sie einen Namen, den kein integrierter Befehl verwendet. Geben Sie / in einer Sitzung ein, um sie zu sehen. $.command.register wirft einen Fehler für einen verwendeten Namen mit einer Nachricht wie "/focus" refused: it is the built-in /focus". Ein Hook, der einen Fehler wirft, wird übersprungen, sodass der Rest Ihres session.start Hooks auch nicht ausgeführt wird. Registrieren Sie Befehle zuletzt in diesem Hook oder wickeln Sie den Aufruf in try und catch.

Fügen Sie ein Tool hinzu

Ein Tool ist für Claude. Registrieren Sie es mit einem Namen, einer Beschreibung, die Claude liest, und einem JSON Schema für seine Eingabe. Claude sieht es unter einem längeren Namen, der aus mcp__, dem Namen Ihres Plugins, zwei Unterstrichen und dem Namen besteht, den Sie registriert haben. Sie behandeln seine Aufrufe in einem tool.call Hook, der auf diesen vollständigen Namen gefiltert ist. Dieses Beispiel aus einem Plugin namens my-mod registriert ticket, sodass der vollständige Name mcp__my-mod__ticket ist. Es gibt Claude ein Tool, das ein Ticket in einem Issue-Tracker nachschlägt:

on('session.start', async ($, e, next) => {
  await $.tool.register({
    name: 'ticket',
    // Claude decides when to call the tool from this description
    description: 'Look up a ticket by its id and return its title and status',
    // The arguments Claude has to send: one required string named id
    inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
  })
  return next(e)
})

// The full tool name is mcp__, the plugin's name, and the registered name
on('tool.call', { tool: 'mcp__my-mod__ticket' }, async ($, e) => {
  // The tool's arguments are fields of e, so the id is e.id
  const response = await $.http.fetch('https://tickets.example.com/api/' + encodeURIComponent(e.id))
  // Return a result either way, so Claude learns when the lookup failed
  return { result: response.ok ? response.text : 'Lookup failed with status ' + response.status }
})

Wenn Sie nach einem Ticket fragen, kann Claude mcp__my-mod__ticket mit seiner ID aufrufen. Der zweite Hook ruft das Ticket ab und gibt den Antwortkörper zurück, den Claude als Ergebnis des Tools liest. Wenn der Server mit einem Fehlerstatus antwortet, liest Claude Lookup failed with status und die Nummer.

Rufen Sie ein Modell auf

Ein Mod kann ein Modell eine Frage stellen, außerhalb des Gesprächs, für eine kleine Aufgabe wie das Sortieren oder Zusammenfassen eines Textstücks. $.model.complete sendet eine Eingabeaufforderung an ein Modell mit den Anmeldedaten Ihrer Sitzung und wird in die Antwort aufgelöst. Es hat keine Gesprächsverlauf.

Dieser Hook beantwortet einen /triage Befehl, registriert als Befehl, indem er ein kleines Modell fragt, den nach dem Befehl eingegebenen Text zu kennzeichnen:

on('command.run', { command: 'triage' }, async ($, e) => {
  const r = await $.model.complete({
    model: 'haiku',
    // The system prompt sets the job, and the prompt carries the text to label
    system: 'Reply with one word: bug, feature, or question.',
    prompt: e.args,
    // One word needs few tokens, and the call gives up after 15 seconds
    maxTokens: 20,
    timeoutMs: 15000,
  })
  // r.text exists only when the model answered, so check r.isAnswered first
  const label = r.isAnswered ? r.text.trim() : 'unknown'
  return { text: 'Label: ' + label }
})

Wenn Sie /triage the export button does nothing ausführen, sendet der Mod diesen Text an das Modell und druckt seine Antwort, wie Label: bug. Claudes Gespräch ist nicht Teil der Anfrage. Wenn das Modell nicht antwortet, ist die Bezeichnung unknown.

Ein Claude API Fehler lehnt den Aufruf nicht ab, daher überprüfen Sie r.isAnswered und lesen Sie r.reason, wenn es false ist. Der Aufruf lehnt nur für eine Anfrage ab, die Claude Code nicht sendet, wie ein Modell, das Ihre Organisation blockiert. Die Typen für Ihren Build listen die anderen Optionen auf, wie effort, und die Limits geben den maxTokens Standard an.

$.model.fork({ prompt }) stellt stattdessen eine Frage über das aktuelle Gespräch, mit demselben Modell und Systemaufforderung, sodass die Claude API die meisten davon aus dem Prompt Cache bedient.

Diese Aufrufe verwenden den Plan oder API-Schlüssel des Benutzers.

Führen Sie Arbeiten im Hintergrund aus

Arbeiten, die ein Ereignis überdauern, wie das Überprüfen von etwas einmal pro Minute, laufen auf einem Timer, den Sie von session.start starten. Ein Hook selbst läuft für ein Ereignis und hat ein Zeitlimit von 10 Sekunden seiner eigenen Laufzeit. Die Zeit, die auf next oder auf einen mods API Aufruf wartet, zählt nicht, außer einem $.clock.sleep. $.clock.every und $.clock.after ersetzen setInterval und setTimeout, mit der Verzögerung in Millisekunden zuerst: $.clock.after(5000, fn) ruft fn einmal auf, fünf Sekunden von jetzt an. Jeder gibt einen Timer mit einer cancel() Methode zurück, und await $.clock.now() gibt die Zeit in Millisekunden an.

Dieser Hook schlägt die Überprüfungen eines Pull Requests einmal pro Minute nach und zeigt das Ergebnis unter der Eingabeaufforderung an. summarize ist eine Funktion Ihres eigenen, die die JSON-Ausgabe des Befehls in ein paar Wörter umwandelt:

on('session.start', async ($, e, next) => {
  // Call the function every 60,000 milliseconds, starting one minute from now
  $.clock.every(60_000, async () => {
    const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])
    // Replace the line under the prompt with the latest summary
    $.ui.status('checks: ' + summarize(status.stdout))
  })
  // Return without waiting for the timer, so the session starts right away
  return next(e)
})

Die Sitzung startet wie gewohnt. Eine Minute später erscheint eine Zeile unter der Eingabeaufforderung mit einem ⚠, dem Namen des Mods und dann checks: und Ihrer Zusammenfassung. Sie wird einmal pro Minute danach ersetzt. Der Callback des Timers läuft außerhalb eines Ereignisses, sodass er zwischen Runden weiterläuft und keinen startet. Wenn der Callback einen Fehler wirft, geht der Fehler zum Debug-Protokoll und der Timer läuft beim nächsten Intervall erneut.

Zeigen Sie etwas an, ohne eine Runde zu starten

Ein Hintergrund-Job kann dem Benutzer etwas anzeigen, ohne eine Runde zu starten. Jeder dieser Aufrufe setzt Text an einen anderen Ort:

Aufruf Was der Benutzer sieht
$.ui.status(text) Eine Zeile unter der Eingabeaufforderung, die bleibt, bis Sie sie ändern. Sie beginnt mit ⚠ und dem Namen des Mods, wie in ⚠ my-mod: checks: 3 passing.
$.ui.toast(text) Ein kleines Feld oben rechts, mit dem Namen des Mods über dem Text, das nach ein paar Sekunden verschwindet
$.ui.log(text) Eine schwache Zeile im Transkript, die Claude nicht liest. Sie beginnt mit ● und dem Namen des Mods, wie in ● my-mod: build finished.

Starten Sie eine Runde aus einem Hintergrund-Job

Wenn ein Hintergrund-Job etwas findet, das Claudes Aufmerksamkeit benötigt, kann er eine Runde starten, indem er eine Eingabeaufforderung mit $.prompt.submit({ text }) einreicht. Claude liest den Text nach einem Satz, der Ihren Mod als Absender benennt. Um ihn als die eigenen Worte des Benutzers zu senden, ohne diesen Satz, fügen Sie asUser: true hinzu. Der Aufruf wartet, bis die Sitzung untätig ist, und startet dann eine neue Runde. Er wird aufgelöst, wenn diese Runde startet, daher await ihn nicht in einem Handler, der läuft, während Claude arbeitet.

Beenden Sie Hintergrund-Arbeiten

Hintergrund-Arbeiten enden auf zwei Arten. Timer enden, wenn das Modul neu geladen wird. Für lang laufende Arbeiten in einem Hook ist next.signal ein AbortSignal, das abbricht, wenn das Ereignis, das Ihr Hook behandelt, aufgegeben wird, zum Beispiel wenn der Benutzer unterbricht, daher übergeben Sie es an alles, das lang läuft.

Senden und empfangen Sie Nachrichten zwischen Sitzungen

Ein Mod kann eine Klartextnachricht an eine andere Ihrer Sitzungen oder an einen der Subagenten dieser Sitzung senden und die Nachrichten beobachten, die ankommen und gehen. $.session.send({ to, text }) sendet eine, die gleiche Lieferung, die das SendMessage Tool macht. to ist { sessionId } für eine Sitzung, { agentId } für einen Subagenten von $.agent.list() oder die Zeichenkettenadresse, von der eine empfangene Nachricht kam. Der Aufruf wird aufgelöst, sobald die Nachricht in die Warteschlange eingereiht ist, mit { isDelivered: true }. Wenn nichts geliefert wurde, wird es mit { isDelivered: false, reason } aufgelöst, und reason sagt, warum.

Dieser Hook beantwortet einen /ping Befehl, registriert als Befehl, indem er die Sitzung fragt, deren ID Sie nach ihm eingeben, um einen Status zu erhalten:

on('command.run', { command: 'ping' }, async ($, e) => {
  // e.args is the session id typed after /ping
  const sent = await $.session.send({ to: { sessionId: e.args }, text: 'Status? One line.' })
  // The call resolves either way, so check isDelivered to learn what happened
  if (!sent.isDelivered) $.ui.toast('Not delivered: ' + sent.reason)
  // An empty result prints nothing in this session's transcript
  return {}
})

Wenn die Nachricht in die Warteschlange eingereiht wird, erscheint nichts in Ihrer Sitzung, und Claudes andere Sitzung liest Status? One line. Wenn nichts geliefert wurde, gibt ein kleines Feld oben rechts den Grund an und verschwindet nach ein paar Sekunden.

Zwei Ereignisse lassen einen Mod die Nachrichten beobachten. Geben Sie next(e) von beiden zurück, um jede Nachricht unverändert durchzuleiten:

Ereignis Wird ausgelöst, wenn Nützliche Felder
session.receive Eine Nachricht kommt für diese Sitzung an, bevor Claude sie liest e.text und e.origin.kind, wie peer oder peer-send-message für eine andere Sitzung oder einen Agenten, task-notification oder scheduled-trigger. Geben Sie { consumed: reason } zurück, um sie von Claude fernzuhalten.
session.send Eine Nachricht ist dabei zu gehen, vom SendMessage Tool oder einem Mod e.to, e.text und e.origin.kind, das model oder plugin ist

Eine Sitzung, die auf eingehende Nachrichten ablehnen eingestellt ist, lehnt eine Nachricht ab, bevor session.receive ausgelöst wird, daher sieht ein Hook sie nie. Eine Nachricht, die für Ihre Genehmigung gehalten wird, erreicht zuerst den Hook, daher kann ein Mod eine Nachricht lesen, die Sie noch nicht genehmigt haben. Der next(e) des Hooks lehnt ab, wenn die Nachricht nicht geliefert wird.

Der Name des Absenders auf einer empfangenen Nachricht ist das, was der Absender geschrieben hat, daher treffen Sie keine Entscheidung darauf.

Erreichen Sie Dateien, Prozesse und das Netzwerk

Ein Mod erreicht das Dateisystem, Prozesse und das Netzwerk durch die mods API, mit den gleichen Berechtigungen wie der Benutzer, der Claude Code ausführt. Das Hooks-Modul selbst hat keine Node.js APIs, keine Timer-Globale wie setTimeout und keinen Netzwerk- oder Dateizugriff. Standard-JavaScript und Web-APIs wie URL, TextEncoder, AbortController und crypto.subtle sind verfügbar. Jeder Namespace unten behandelt eine Art von Zugriff:

Namespace Was es tut
$.fs read(path), write(path, text), exists(path), stat(path) und list(path) arbeiten mit Dateien und Verzeichnissen
$.process run(['git', 'status']) startet einen Befehl und wird aufgelöst, wenn er beendet wird. spawn streamt die Ausgabe eines lang laufenden Befehls.
$.http fetch(url, init) über http oder https. Es wird aufgelöst zu { status, ok, headers, text }, sobald der Körper gelesen wird.
$.store Ein JSON-Schlüssel-Wert-Speicher Ihres eigenen Plugins, der zwischen Sitzungen beibehalten wird
$.env get und set Umgebungsvariablen. Schreiben Sie den Namen als Literalzeichenkette.
$.settings read was die Einstellungsdateien und verwaltete Richtlinie halten
$.session messages() gibt das Transkript als eine Liste von { role, text, toolUses } zurück. Auch das Arbeitsverzeichnis, Modell und mehr. usage() gibt die Nutzung des Kontextfensters und Planlimits zurück.
$.mcp call ein Tool auf einem verbundenen MCP Server

Dateien und Prozesse haben ein paar Regeln ihrer eigenen:

  • Pfade: ein relativer Pfad ist unter dem Arbeitsverzeichnis der Sitzung
  • $.fs.list: gibt die Einträge eines Verzeichnisses als { name, kind, size, isLink } zurück und steigt nicht in Unterverzeichnisse ab
  • $.process.run: nimmt eine Argumentliste und verwendet keine Shell. Es wird aufgelöst zu { exitCode, stdout, stderr } unabhängig vom Exit-Code. Es lehnt ab, wenn das Programm nicht starten kann oder beim Timeout noch läuft, das standardmäßig 30 Sekunden beträgt, daher wickeln Sie es in try und catch.

Jeder dieser Aufrufe ist selbst ein Ereignis, benannt nach seinem Namespace und seiner Methode ohne das $., wie fs.read für $.fs.read. Ein Mod früher in der Kette kann Ihren Aufruf beobachten, umschreiben oder ablehnen, was ist, wie eine Organisation einschränkt, was Mods erreichen.

Nächste Schritte