Rezepte für Agenten und Skripte
Steuere verbatra aus einer Agenten-Schleife oder einem Shell-Skript: welche Befehle JSON ausgeben, wie die Nutzdaten wirklich aussehen und wie du darauf verzweigst.
Maschinell übersetzte Seite
Ein Agent oder ein Skript braucht drei Dinge von einer CLI: eine stabile Art zu fragen, eine parsebare Antwort und ein eindeutiges Signal, was passiert ist. verbatra gibt dir alle drei über --json und den Exit-Code. Diese Seite ist die ausgearbeitete Fassung davon: echte aufgezeichnete Nutzdaten und kurze Rezepte, die ausführen, parsen, entscheiden und handeln.
Sie wiederholt den Vertrag nicht. Der --json-Envelope und jede result-Form sind in der CI-Anleitung dokumentiert, die CLI-Übersicht nennt die Konvention, und jede Befehlsseite trägt ihre eigene Exit-Code-Tabelle. Lies dort, was ein Feld bedeutet; lies hier, was du damit machst.
Wenn du einen Agenten willst, der das Dashboard im Browser bedient statt einen Prozess in einer Shell, ist das eine andere Oberfläche: siehe Studio mit einem Browser-Agenten bedienen.
Die Rezepte unten lesen stdout mit jq, weil das das Kleinste ist, was einen JSON-Strom aus einer Shell liest. Jeder JSON-Parser tut es; nichts an der Ausgabe hängt an jq.
Welche Befehle JSON ausgeben
| Befehl | --json | Ein erfolgreicher Datensatz trägt |
|---|---|---|
translate | ja | ein RunSummary |
watch | ja | ein RunSummary pro Lauf, als NDJSON |
check | ja | ein CheckSummary |
diff | ja | ein DiffSummary |
doctor | ja | ein DoctorResult |
export | ja | den geschriebenen Pfad und die Zeilenzahl pro Locale |
import | ja | ein RunSummary |
init | nein | |
studio | nein |
init legt interaktiv ein Projekt an und studio ist ein langlaufender Server, deshalb hat keiner von beiden einen maschinenlesbaren Modus. Alles andere, was ein Agent von einem Projekt braucht, ist über die anderen sieben erreichbar.
doctor ist der jüngste Zugang zu dieser Liste.
Verfügbar ab 0.9.0
Auf stdout stehen Envelopes und sonst nichts
Datensätze zu Fortschritt und Lock-Wartezeit sowie die menschenlesbare Fehlerzeile gehen in beiden Modi auf stderr. Leite stderr um, und stdout ist ein sauberer Strom aus JSON-Datensätzen:
verbatra check --json 2>/dev/nullBehalte stderr, wenn du ein Protokoll davon willst, was der Lauf gerade getan hat:
verbatra translate --json 2>run.log | jq .Drei Nutzdaten, aufgezeichnet
Jede Ausgabe unten stammt echt aus einem kleinen Projekt: Quell-Locale en, ein Ziel-Locale de und eine de-Datei, die app.title bereits hat, app.greeting aber nicht.
verbatra check --json beantwortet "läuft irgendetwas auseinander", in Zahlen:
verbatra check --json 2>/dev/null{"ok":true,"version":1,"command":"check","result":{"inSync":false,"locales":[{"locale":"de","missing":1,"stale":0,"upToDate":1,"inSync":false}]}}Der Befehl endet mit 1, weil inSync false ist. Das ist das günstigste Gate überhaupt: Der Exit-Code allein sagt dir, ob du die Nutzdaten überhaupt lesen musst.
verbatra diff --json beantwortet dieselbe Frage über Key-Namen statt über Zahlen:
verbatra diff --json 2>/dev/null{"ok":true,"version":1,"command":"diff","result":{"hasPendingChanges":true,"locales":[{"locale":"de","missing":["app.greeting"],"changed":[],"orphaned":[],"hasPendingChanges":true}]}}Ebenfalls Exit 1. Nimm diff, wenn der Agent die Keys benennen muss (um einen Pull-Request-Kommentar zu schreiben oder um zu entscheiden, ob sich die Arbeit lohnt); nimm check, wenn ein Ja oder Nein reicht.
verbatra translate --dry-run --json beantwortet "was würde ein Lauf tun", ohne Provider-Aufruf, ohne API-Key und ohne irgendetwas zu schreiben. Zum Lesen hier formatiert; auf der Leitung ist es eine einzelne Zeile wie die beiden oben:
verbatra translate --dry-run --json 2>/dev/null | jq .{
"ok": true,
"version": 1,
"command": "translate",
"result": {
"dryRun": true,
"locales": [
{
"locale": "de",
"status": "succeeded",
"translated": [
"app.greeting"
],
"unchanged": [
"app.title"
],
"orphaned": [],
"pruned": [],
"invalidIcuSource": [],
"cacheHits": [],
"integrityMismatches": [],
"providerFailures": [],
"budgetWithheld": [],
"generated": [],
"notices": [],
"needsReview": [],
"unfilled": [],
"malformedRows": [],
"duplicateKeys": []
}
],
"succeeded": [
"de"
],
"partial": [],
"failed": []
}
}Exit 0. Jede Liste pro Locale ist auch dann vorhanden, wenn sie leer ist, ein Agent kann also darauf zugreifen, ohne vorher zu prüfen. Ein echter Lauf ergänzt pro Locale und in der Zusammenfassung ein usage-Objekt, sofern der Provider einen Verbrauch meldet; ein Dry-Run nie, weil er keinen Aufruf macht. Die vollständige Anatomie eines LocaleSummary steht in der SDK-Referenz.
Wenn ein Lauf überhaupt nicht stattfinden kann, stehen dort stattdessen Fehlerdaten, und der Befehl endet mit 2:
{"ok":false,"version":1,"command":"check","code":"CONFIG_INVALID","message":"The verbatra configuration is invalid: provider.options.maxOutputTokens: Invalid input: expected number, received undefined"}code ist der stabile Teil. Verzweige darauf, nicht auf message.
ok: true heißt nicht, dass alles übersetzt wurde
Das ist die Stelle, an der Skripte auflaufen. Ein Locale kann innerhalb eines Laufs scheitern, der selbst durchgekommen ist. Der Envelope bleibt ok: true, weil der Befehl lief und eine Zusammenfassung erzeugt hat; der Fehlschlag taucht in result.failed und result.partial auf, und der Exit-Code ist 1.
Hier ist translate gegen einen nicht erreichbaren Provider-Endpunkt:
verbatra translate --json 2>/dev/null | jq -c '{ok, succeeded: .result.succeeded, partial: .result.partial, failed: .result.failed}'{"ok":true,"succeeded":[],"partial":[],"failed":["de"]}ok beantwortet "ist der Befehl gelaufen". succeeded, partial und failed beantworten "ist die Arbeit angekommen". Der Exit-Code fasst beides bereits zusammen, deshalb liegt ein Agent, der nur den Exit-Code liest, nie falsch, und ein Agent, der nur ok liest, liegt beim ersten schlechten Tag eines Providers falsch.
Auf den Exit-Code verzweigen
Drei Codes decken jeden einmaligen Befehl ab. Dieses Gate liest check, gibt die auseinandergelaufenen Locales aus, wenn es welche gibt, und behandelt einen Fehler des gesamten Laufs als ein Problem anderer Art:
#!/usr/bin/env bash
set -uo pipefail
report=$(verbatra check --json 2>/dev/null)
status=$?
case $status in
0)
# in sync: nothing to do
echo "every locale is in sync"
;;
1)
# it ran, the result is not clean: the payload says what drifted
echo "$report" | jq -r '.result.locales[] | select(.inSync | not) | "\(.locale): \(.missing) missing, \(.stale) stale"'
;;
2)
# it could not run: the payload is an error envelope
echo "$report" | jq -r '"cannot run [\(.code)] \(.message)"' >&2
exit 2
;;
esacIm selben Projekt liefern die drei Zweige:
de: 1 missing, 0 stale
every locale is in sync
cannot run [CONFIG_INVALID] The verbatra configuration is invalid: provider.options.maxOutputTokens: Invalid input: expected number, received undefinedFühre das nicht unter set -e aus: Ein Exit-Code ungleich null ist genau das Signal, wegen dem du hier bist, kein Absturz. Code 130 kommt hier nie vor, denn nur watch und studio lassen sich durch einen zweiten Interrupt zwangsbeenden. Die vollständige Tabelle steht in CI und Exit-Codes.
watch ist ein Strom, keine einzelne Ausgabe
watch --json ist von anderer Art als die übrigen sechs. Es gibt für die Lebensdauer des Prozesses einen Envelope pro Lauf aus, als NDJSON: ein JSON-Objekt pro Zeile, kein umschließendes Array, kein Abschluss. Ein Konsument liest zeilenweise und liest weiter.
Roh, zwei Läufe einer Sitzung (der zweite, nachdem die Quelldatei mit einem Syntaxfehler gespeichert wurde):
{"ok":true,"version":1,"command":"watch","result":{"dryRun":false,"locales":[{"locale":"de","status":"succeeded","translated":["app.greeting"],"unchanged":["app.title"],"orphaned":[],"pruned":[],"invalidIcuSource":[],"cacheHits":[],"integrityMismatches":[],"providerFailures":[],"budgetWithheld":[],"generated":[],"notices":[],"needsReview":[],"unfilled":[],"malformedRows":[],"duplicateKeys":[],"usage":{"inputTokens":120,"outputTokens":40}}],"succeeded":["de"],"partial":[],"failed":[],"usage":{"inputTokens":120,"outputTokens":40}}}
{"ok":false,"version":1,"command":"watch","code":"SOURCE_INVALID","message":"The source locale file at /home/dev/app/locales/en.json could not be read: The file is not valid JSON."}Beide Datensatzarten erscheinen im selben Strom, reduziere also jede Zeile auf die Felder, auf die du reagierst. --unbuffered lässt jq pro Zeile ausgeben, und genau das macht aus der Pipe etwas, worauf du reagieren kannst, statt etwas, das du am Ende liest:
verbatra watch --json 2>/dev/null \
| jq -c --unbuffered '{ok, code, failed: (.result.failed // null), translated: [(.result.locales // [])[].translated[]]}'Drei Läufe einer Sitzung (Änderung, Änderung, dann eine kaputte Quelldatei):
{"ok":true,"code":null,"failed":[],"translated":["app.greeting"]}
{"ok":true,"code":null,"failed":[],"translated":["app.logout"]}
{"ok":false,"code":"SOURCE_INVALID","failed":null,"translated":[]}Aus dieser letzten Zeile folgen zwei Dinge. Ein fehlgeschlagener Lauf ist ein Datensatz im Strom, nicht das Ende der Sitzung: Der Watcher blieb oben und übersetzte danach weiter. Und er ändert nie den Exit-Code, der allein davon abhängt, wie die Sitzung endet. Ein langlebiger Agent behandelt eine ok: false-Zeile deshalb als meldenswertes Ereignis, nicht als Grund, den Prozess neu zu starten.
Eine ausgearbeitete Schleife: absichern, entscheiden, übersetzen, melden
Alles zusammengesetzt. Das läuft unbeaufsichtigt, verweigert Ausgaben für einen Auftrag, der zu groß ist, um ihn einem Roboter zu überlassen, und meldet, was wirklich angekommen ist, statt dessen, was angefragt wurde:
#!/usr/bin/env bash
set -uo pipefail
# 1. Run: ask what is pending. Read-only, no provider call, no API key.
pending=$(verbatra diff --json 2>/dev/null)
case $? in
0) echo "nothing pending"; exit 0 ;;
2) echo "$pending" | jq -r '"cannot run [\(.code)] \(.message)"' >&2; exit 2 ;;
esac
# 2. Parse: which locales have work, and how much.
echo "$pending" | jq -r '.result.locales[] | select(.hasPendingChanges)
| "\(.locale): \((.missing + .changed) | length) pending"'
# 3. Decide: only spend unattended when the job is small.
keys=$(echo "$pending" | jq '[.result.locales[] | .missing + .changed] | flatten | length')
if [ "$keys" -gt 200 ]; then
echo "$keys pending keys is above the unattended limit; run this by hand" >&2
exit 1
fi
# 4. Act: translate, then report what landed, not what was asked for.
summary=$(verbatra translate --json 2>/dev/null)
status=$?
echo "$summary" | jq -r 'if .ok | not then "run error [\(.code)] \(.message)"
else [ { label: "succeeded", locales: .result.succeeded },
{ label: "partial", locales: .result.partial },
{ label: "failed", locales: .result.failed } ]
| map(select(.locales | length > 0) | "\(.label): \(.locales | join(", "))")
| join(" | ")
end'
exit $statusDie drei Ausgänge in einem Projekt mit einem ausstehenden Key in de:
de: 1 pending
succeeded: dede: 1 pending
failed: denothing pendingDer erste endet mit 0, der zweite mit 1 (der Provider-Endpunkt war nicht erreichbar), der dritte mit 0, ohne etwas auszugeben. Schritt 1 nutzt bewusst diff statt translate --dry-run: Beide sind schreibgeschützt und brauchen keinen Key, aber diff ist die genau dafür gebaute Frage, und sein Exit-Code allein beantwortet schon, ob es Arbeit gibt.
Der Provider-Key für Schritt 4 kommt aus der Umgebung, wie immer. Lege ihn in deinem CI-Secret-Store oder deiner Shell-Umgebung ab und lass ihn den Prozess erben; die CLI nimmt kein Key-Argument entgegen und liest keinen aus der Konfigurationsdatei. Welche Variable dein Provider liest, steht unter Provider.
Als Nächstes
- CI und Exit-Codes: der Envelope, die Exit-Code-Tabelle und jede
result-Form vollständig. - CLI-Übersicht: die gemeinsamen Flags und wie die Binary deine Umgebung liest.
verbatra doctor: die günstigste Vorabprüfung vor einem unbeaufsichtigten Lauf.- Studio mit einem Browser-Agenten bedienen: dasselbe Projekt, bedient aus einem authentifizierten Dashboard-Tab statt aus einer Shell.
- Das SDK: überspringe die Prozessgrenze und rufe
translate,checkunddiffdirekt aus TypeScript auf.
Review in Studio
Starte Verbatra Studio, arbeite die Review-Warteschlange ab, korrigiere Einträge direkt und lass markierte Keys aus dem lokalen Dashboard neu übersetzen.
Studio mit einem Browser-Agenten bedienen
Mach die Review-Aktionen von Verbatra Studio als WebMCP-Tools verfügbar, damit ein KI-Agent im Browser dieselben Operationen aus deinem offenen, authentifizierten Dashboard-Tab heraus steuern kann.