Zum Inhalt springen

Handbuch

Hooks erstellen

CLI-Ereignisse mit Command- und HTTP-Hooks begleiten, Payloads verstehen und tatsächliche Blockiergrenzen prüfen.

Geprüfter CLI-Quellstand: 2.4.0 · Redaktionell aktualisiert:

Aufbau und Voraussetzungen

Hooks reagieren auf CLI-Ereignisse. Sie sind reale Prozess- oder Netzwerkaktionen, keine Promptvorlagen. Projektquellen benötigen Zustimmung; globale Hooks gelten als vertrauenswürdige Benutzerquelle. Prüfen Sie ausführbaren Inhalt, Ziel und Datenfluss vor einer Freigabe.

Die Datei .lurus/hooks.json enthält direkt eine Eventmap, keine zusätzliche äußere hooks-Ebene. Für globalen Einsatz verwenden Sie ~/.lurus/hooks.json. Alternativ können Sie in den Einstellungen hooks sowie – abhängig vom Geltungsbereich – globalHooks oder projectHooks verwenden. Eventarrays aus diesen Quellen werden aneinander angehängt; verschachtelte Einstellungen werden nicht allgemein zusammengeführt. Gehen Sie nicht davon aus, dass Änderungen automatisch neu geladen werden. Starten Sie danach eine neue CLI-Sitzung.

Command-Hooks erhalten auf stdin ein JSON-Objekt:

{
  "event": "PreToolUse",
  "context": { "toolName": "Write", "toolInput": { "path": "example.txt" } },
  "cwd": "/path/to/project"
}

toolInput ist der tatsächlich vom Tool verwendete Input, keine für alle Werkzeuge einheitliche Form. Ausführung im richtigen Projekt-CWD und vorhandenes node sind Voraussetzungen der folgenden Beispiele. Nicht in echten Nutzerglobals experimentieren.

Write gezielt blockieren

Legen Sie .lurus/hooks/block-write.cjs an:

process.stdin.resume();
process.stdin.on('end', () => {
  console.log(
    JSON.stringify({
      decision: 'deny',
      reason: 'Write is blocked by this project hook.',
    }),
  );
});

Und .lurus/hooks.json:

{
  "PreToolUse": [
    {
      "matcher": "^Write$",
      "hooks": [
        {
          "type": "command",
          "command": "node .lurus/hooks/block-write.cjs",
          "timeout": 30000
        }
      ]
    }
  ]
}

Nach Neustart und bewusster Projektfreigabe wird ein Write-Aufruf durch deny verhindert. Edit, MultiEdit, Shellprozesse und andere Mutationen sind damit nicht gesperrt. Dieses Beispiel ist kein universelles Schreib- oder Securitygate. Global ist dieselbe Eventstruktur möglich; verwenden Sie im globalen Befehl einen tatsächlich auflösbaren geprüften Scriptpfad, nicht einen Projektpfad, der in anderen Projekten fehlt.

Toolereignisse wirklich protokollieren

Nur stderr auszugeben erzeugt nicht automatisch eine nutzerseitig sichtbare Logdatei. Ein vollständiges Beispiel .lurus/hooks/log-tool.cjs schreibt bewusst ausschließlich Metadaten:

const { appendFileSync } = require('node:fs');
const { join } = require('node:path');

let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
  input += chunk;
});
process.stdin.on('end', () => {
  const payload = JSON.parse(input);
  const record = {
    event: payload.event,
    toolName: payload.context.toolName,
    isError: payload.context.isError,
  };
  appendFileSync(
    join(payload.cwd, '.lurus/hooks/tool-events.log'),
    JSON.stringify(record) + '\n',
  );
  console.log(JSON.stringify({ decision: 'allow' }));
});

Ergänzen Sie im selben .lurus/hooks.json das Event:

{
  "PostToolUse": [
    {
      "matcher": "^(Write|Edit)$",
      "hooks": [
        {
          "type": "command",
          "command": "node .lurus/hooks/log-tool.cjs",
          "timeout": 30000
        }
      ]
    }
  ]
}

Die beiden Beispiele sind getrennte vollständige Eventzuordnungen. Für gemeinsame Verwendung übernehmen Sie beide Eventschlüssel in ein JSON-Objekt. Nach passenden erfolgreichen Toolaufrufen werden Zeilen in .lurus/hooks/tool-events.log angehängt. Verwenden Sie einen vorhandenen Hooksordner und protokollieren Sie keine Tooleingaben, Ergebnisse, Tokens oder Secrets. Ein Logfehler stoppt die Ausführung nicht zuverlässig; siehe Fail-open.

Alle Events und tatsächliche Wirkung

Jeder Hook erhält {event, context, cwd}. Definitionen und Aktionen laufen nacheinander; deny stoppt die restlichen Hooks. Nur dafür vorgesehene Events blockieren auch die CLI-Aktion, wie in der Tabelle beschrieben.

EventContextfelderTatsächliche Wirkung
SessionStartsessionId, trigger: startupBegleitet den Start, blockiert ihn aber nicht; Zusatzkontext wird dabei nicht übernommen.
SessionEndsessionId, trigger: exitBegleitet das Beenden, ohne es zu blockieren.
PreToolUsetoolName, toolInputdeny verhindert den Toolaufruf; Zusatzkontext wird verarbeitet.
PostToolUsetoolName, toolResult, isError: falseZusatzkontext; keine Rücknahme einer bereits geschehenen Aktion.
PostToolUseFailuretoolName, toolResult, isError: trueZusatzkontext nach Fehler; nicht jeder frühe Abbruch feuert dieses Event.
UserPromptSubmitpromptdeny stoppt den Prompt; Zusatzkontext wird verarbeitet.
SubagentStartsubagentId, subagentTypeBegleitet den Start eines Unteragenten, ohne ihn zu blockieren.
SubagentStopsubagentId, subagentTypeBegleitet den Abschluss, ohne Ergebnisse zurückzunehmen.
Stoptrigger: stopÜbernimmt Zusatzkontext einmalig; ein weiterer Agentlauf ist nicht garantiert.
PreCompacttrigger: compact, messageCount, estimatedTokens, filesReadBegleitet die Komprimierung, ohne sie zu blockieren.

Matcher und Felder

Der Matcher wird gegen context.toolName, sonst context.trigger geprüft. Fehlen beide, passt jeder Matcher. Leer und * passen immer. Ohne Stern wird erst exakt, dann als Regex verglichen; mit Stern entsteht eine verankerte Regexumsetzung, andere Sonderzeichen regulärer Ausdrücke werden nicht automatisch maskiert. Dies ist kein Standard-Globmatcher. Verwenden Sie beispielsweise ^(Write|Edit)$, um eine konkrete Toolgruppe auszuwählen.

FeldTyp / Default
<Event>Array von Definitionen, Eventname in JSON exakt schreiben.
matcherOptionaler string wie ^Write$.
hooksArray von Actions pro Definition.
type: commandcommand: string, optional timeout: number in ms.
type: httpurl: string, optional method: POST|PUT|PATCH, headers: Record<string,string>, timeout: number.
timeoutStandardmäßig 30.000 ms pro Aktion, kein Gesamtlaufbudget.
methodStandardmäßig POST.

Antwortvertrag und Fail-open

stdout gehört dem Antwortvertrag, Diagnose auf stderr:

{
  "decision": "deny",
  "reason": "Explain why the action is blocked",
  "additionalContext": "Optional guidance for the agent"
}
  • Exit 0: JSON mit decision: allow|deny, optional reason/additionalContext.
  • Exit 2: deny; Grund aus stderr/stdout.
  • ask ist kein geparster Entscheidungswert.
  • Ungültiges JSON, andere Exitcodes, Ausführungsfehler und HTTP-Statuscodes außerhalb von 2xx führen zu Fail-open: Die Aktion wird nicht automatisch blockiert.
  • HTTP-Antworten mit 2xx-Status verwenden denselben JSON-Antwortvertrag.
  • Shelltimeout sendet SIGTERM; kein garantiertes hartes Beenden aller Kindprozesse.

Ein fehlender Hook, Scriptfehler oder Timeout ist deshalb kein verlässlicher Schutzwall. Secrets, Pfade und Prozessrechte müssen zusätzlich durch die eigentlichen Permissions geschützt sein.

HTTP-Hook

Für einen bereits vorhandenen bewusst lokalen Entwicklerendpoint, der JSON antwortet:

{
  "UserPromptSubmit": [
    {
      "hooks": [
        {
          "type": "http",
          "url": "http://127.0.0.1:8765/hooks/prompt",
          "method": "POST",
          "headers": { "Content-Type": "application/json" },
          "timeout": 30000
        }
      ]
    }
  ]
}

Der Requestbody enthält die Eventdaten, hier also auch den Prompt. Der vorhandene Endpunkt muss zum Beispiel {"decision":"allow"} mit einem HTTP-Status im 2xx-Bereich zurückgeben. Stellen Sie nicht auf einen fremden öffentlichen Endpunkt um, ohne die Datenfreigabe zu prüfen. Authorization, Cookie, Proxy-Authorization, X-API-Key und CRLF in Headerwerten sind gesperrt; keine Authsecrets als Hookheader einchecken.

Generator, Validierung und Umgebung

Achtung: Der Generator schreibt Projektdateien und kann gleichnamige Skripte überschreiben. Prüfen Sie Namen und vorhandene Dateien vor der Ausführung.

/create-hook PreToolUse block-write
/create-hook PostToolUse log-tool --ts
/hooks validate

Der Generator erstellt Projektdateien und ergänzt hooks.json, wenn für das Event eine Vorlage vorhanden ist. Bei der Eventeingabe wird Großschreibung ignoriert; in JSON müssen die Namen exakt stimmen. Shellvorlagen werden ausführbar gemacht; TypeScriptvorlagen benötigen npx tsx. Generierte Befehle können absolute Pfade verwenden.

/hooks validate prüft nur einen Teil der Projektkonfiguration, nicht sämtliche Matcher, HTTP-Einstellungen, Timeouts oder Sicherheitswirkungen. Es gibt keine allgemeinen Create- oder Reloadoptionen. Hookprozesse erhalten nur zugelassene Umgebungsvariablen; LURUS_HOOK_PASS_ENV ergänzt kommaseparierte Namen. Jeder weitergereichte Wert ist für den Hookprozess vollständig sichtbar und erfordert bewusste Freigabe.

Fehlersuche

Ein falsches Arbeitsverzeichnis, fehlendes node oder ein fehlender Skriptpfad führt zu einem Ausführungsfehler mit Fail-open. Wird kein Event verarbeitet, prüfen Sie Schreibweise und Matcher. Blockiert deny nicht, prüfen Sie die Eventtabelle; nachträgliche Events können nichts zurücknehmen. Fehlt die Logdatei, prüfen Sie Zielordner und Schreibzugriff. Starten Sie nach Dateiänderungen eine neue Sitzung.

Eine bewusste Entscheidung

Vor dem ersten Prompt.

Interaktive Demo
Lurus Codeprivacy-demo · beispielhafter Workspace

Demo-Sitzung bereit. Modellwahl und Datenweg sind getrennte Entscheidungen.

Inszeniertes Beispiel. Keine echte Anmeldung, Sicherheitsprüfung oder Codeübertragung.

Bildvorschau