Reference / CLI
verbatra init
Create a verbatra config and .env example for this project, detected from your locale files, interactively or fully unattended.
Scaffold a project: init writes a verbatra.config.ts, a .env.example naming your provider's API key variable (never a key value), and the .gitignore entries for .env, .env.local, .verbatra-local/ and verbatra.cache.json. It reads the locale files already in the project first, so usually the provider is all you have to tell it.
Synopsis
verbatra init [flags]Examples
# prompt for anything not passed or detected
verbatra init --provider gemini
# fully unattended, one JSON document on stdout
verbatra init --provider gemini --yes --json
# choose the format and pattern yourself
verbatra init --provider deepl --format yaml --path "config/locales/{locale}.yml" --yes
# a local server that needs no key
verbatra init --provider openai-compatible --base-url http://localhost:11434/v1 --model llama3.1 --yes
# a self-hosted LibreTranslate server, no language model
verbatra init --provider libretranslate --base-url http://localhost:5000 --yes
# human-only: no provider, no API key
verbatra init --provider none --yes
# also set the project up for coding agents
verbatra init --provider gemini --yes --agent
# wire Cursor and VS Code, and only show what would change
verbatra init --agent --client cursor,vscode --dry-runFlags
| Flag | Argument | Default | Effect |
|---|---|---|---|
--provider | <id> | none (required unless asked) | provider to scaffold: anthropic, openai, gemini, deepl, google-translate, opencode, openai-compatible, libretranslate, or none |
--format | <id> | detected, else i18next-json | the locale file format, one of the ids on Formats |
--source | <locale> | detected, else en | the source locale your strings are written in |
--targets | <locales> | detected, else de | comma-separated target locales to translate into |
--path | <pattern> | detected, else the format's usual layout (Detection) | locale file pattern; must contain the {locale} token |
--model | <name> | the scaffold default | the model; required for openai-compatible, optional for anthropic, openai, gemini and opencode |
--base-url | <url> | none | the server URL; openai-compatible and libretranslate only, and required there |
--api-key-env-var | <name> | OPENAI_ | the variable openai-compatible reads its key from |
--yes | none | off | never prompt; take the default for anything neither passed nor detected |
--force | none | off | overwrite an existing verbatra.config.ts whose content differs |
--agent | none | off | also write the coding-agent section to AGENTS.md or CLAUDE.md and the verbatra MCP server to each detected client's project config (Set up coding agents) |
--client | <ids> | detected | comma-separated clients --agent wires instead of the detected ones: claude, cursor, vscode, codex, gemini, or all; needs --agent |
--dry-run | none | off | report every file init would write or change, and write nothing (Dry run) |
Also takes the global flags --cwd and --json, but not --config. Here --cwd is the directory to scan and write to, and it must already exist. --json never prompts.
Behavior
Prompts
A flag always wins. Anything you leave out is detected from the project, then prompted for when stdin is a terminal and neither --yes nor --json is given, and otherwise taken from its default only under --yes. Pressing Enter accepts the suggested value. A flag with an empty value, such as --targets '', is a usage error (INVALID_), never a request for the default. If stdin ends at a prompt (Ctrl-D), init exits 2 with MISSING_.
Each answer is checked as you give it. An invalid one is explained on stderr and asked again, up to three times, keeping your earlier answers; a third invalid answer stops init with that answer's error code (such as INVALID_), names the flag to pass instead, and writes nothing.
Detection
init scans the directory for files a format adapter claims, skipping hidden directories, node_ and build output, and finds the part of each path that spells a locale, such as de.json, messages_, de.lproj/ or Android's values-de/. The file pattern covering the most locales wins, and it fills in:
format: the one format whose adapter reads every matched file. Every JSON adapter reads.json, so for JSON files a single matching dependency inpackage.json(i18next,vue-i18n,next-intlor@ngx-translate/core) decides.files.patternandfiles.localeStyle:posixwhen the files spellpt_,BR androidforvalues-*directories,literalotherwise.sourceLocale: the source language of an.xcstringscatalogue, elseenor the singleen-*locale found, else the only locale found.targetLocales: every other locale found.
A file beside the locale files that holds strings under no locale name, such as messages.properties, Strings.resx or a gettext app.pot, is reported as unqualifiedSourceFile. verbatra cannot read the source from such a file, so init does not guess the source locale: pass --source, even with --yes, and the next steps name the file to rename or copy the base file to.
With no locale file at all, the dependencies alone decide the format, and the pattern follows the format's usual layout, the one listed per format on Pick your stack.
An apple-xcstrings catalogue is one file shared by every locale, and verbatra never creates it: create it in Xcode first. --format, --path and --source narrow the detection instead of being overridden by it, so --format yaml only looks at YAML files.
init never guesses between equals. Several formats that read the files (plain JSON with no i18n dependency) exit 2 with FORMAT_, and several tied file patterns (one file per namespace) exit 2 with LAYOUT_. Both list the candidates; pass --format or --path to choose. At a terminal it asks instead.
Unattended runs
Without a terminal, or with --json, init never prompts. A value that is neither passed, detected, nor defaulted by --yes is an error: init exits 2 with MISSING_ and names every missing flag. An ambiguity or an invalid flag also lists the flags still missing, so one run tells an agent everything it has to pass. --provider has no default, and neither do --base-url and --model for openai-compatible or --base-url for libretranslate. This is the command an agent runs:
npx @verbatra/cli init --provider gemini --yes --jsonJSON output
Schema: init-envelope.json validates the whole envelope; Schemas lists the others.
With --json, stdout carries exactly one envelope. Its result:
interface InitResult {
configPath: string; // absolute path of verbatra.config.ts, or of the config --agent kept
files: { path: string; action: "created" | "overwritten" | "updated" | "unchanged" }[];
dryRun: boolean; // true under --dry-run: files holds the planned actions
config: { // the config that was validated and written
sourceLocale: string;
targetLocales: string[];
format: string;
files: { pattern: string; localeStyle?: "posix" | "android" };
provider: { id: string; options?: Record<string, unknown> };
sensitiveData?: { mode: "warn" };
} | null;
sources: { // where each value came from
provider: "flag" | "prompt";
format: "flag" | "prompt" | "files" | "dependencies" | "default";
pattern: "flag" | "prompt" | "detected" | "default";
sourceLocale: "flag" | "prompt" | "detected" | "default";
targetLocales: "flag" | "prompt" | "detected" | "default";
} | null;
apiKeyEnvVar: string | null; // the variable to set; null for provider none
detection: {
format: { id: string; from: "input" | "files" | "dependencies" } | null;
layout: {
pattern: string;
localeStyle: "literal" | "posix" | "android";
locales: string[];
sourceLocale: string | null;
unqualifiedSourceFile: string | null;
files: string[];
} | null;
ambiguities: { subject: "format" | "layout"; candidates: string[] }[];
confidence: "high" | "medium" | "low" | "none";
reasons: string[]; // one sentence per decision or doubt
} | null;
agent: { // null without --agent
instructionsFile: "AGENTS.md" | "CLAUDE.md";
mcpServer: "added" | "present" | "differs" | "skipped" | null; // Claude Code; null when not wired
configKept: boolean; // true when --agent kept an existing config
clients: {
id: "claude" | "cursor" | "vscode" | "codex" | "gemini";
file: string; // the client's config file, such as .mcp.json
server: "added" | "present" | "differs" | "skipped";
reason: "plugin" | "symlink" | null; // why the client was skipped
selectedBy: "flag" | "markers" | "default";
markers: string[]; // the client's marker files found in the project
}[];
} | null;
nextSteps: { description: string; command: string | null }[];
}confidence is high when the files alone decided everything, medium when a convention or a dependency filled a gap (such as taking en as the source) or a gap stayed open, low when no locale file backed the format, and none when nothing was detected. A default in sources is the value to double-check. nextSteps ends with the exact commands to run next.
An error envelope for FORMAT_, LAYOUT_, INVALID_ or INVALID_ also carries candidates, the values to choose from. Every usage error that leaves flags unanswered carries missing, naming them, such as "--source <locale>".
Providers
--provider nonescaffolds a human-only config with nooptionsand writes no.env.example, since no API key is ever read.--provider openai-compatiblescaffolds a local or self-hosted server from--base-urland--model, withmaxOutputTokens: 4096. Its key is optional: a server that needs none leavesOPENAI_unset, andCOMPATIBLE_ API_ KEY --api-key-env-varnames a different variable, in upper case such asLLM_. A rejected value is never echoed back, in case it was the key itself.API_ KEY --provider libretranslatescaffolds a self-hosted LibreTranslate server from--base-urlalone, such ashttp://localhost:5000, and refuses--modeland--api-key-env-var..env.examplenamesLIBRETRANSLATE_for a server started withAPI_ KEY --api-keys.
init writes a variable's name into the config and .env.example, never a value, and refuses a base URL with credentials, a query string or a fragment (https://user:key@host, https://host/v1?api-key=...), where a key could end up in the committed config.
Existing files
Running init again with the same answers changes nothing and succeeds. A verbatra.config.ts with different content is refused with CONFIG_ unless you pass --force, and that refusal comes before any prompt or other error. Another verbatra config in the directory (a .verbatrarc.json, or a verbatra property in package.json) is refused even under --force, because it would be read first. Nothing is written when init refuses. A parent directory's config covering this one draws a stderr warning (dry run too): the new verbatra.config.ts replaces it here.
A directory init cannot write to is refused with INIT_, naming the file and any file already written. A verbatra.config.ts, .env.example or .gitignore that is a symbolic link leading outside the project, or nowhere, is refused with INIT_ before anything is written. An existing .env.example is kept; when it does not name the key variable yet, that one line is appended, and under --force a header line init wrote itself is rewritten for the new provider. .gitignore gets only the entries it is missing.
Dry run
--dry-run runs every check and refusal of a real run except a failed write, such as a read-only directory, prints would create, would update, would overwrite or unchanged for each file, and writes nothing. The JSON result lists the planned actions and sets dryRun to true.
Set up coding agents
--agent also prepares the project for coding agents. It writes no key value, never writes through a link out of the project, and adds:
- The instruction file. A verbatra section between
<!-- verbatra:start -->and<!-- verbatra:end -->markers, word for word the snippet from Tell your agent about verbatra. It goes toAGENTS.md, or toCLAUDE.mdwhen that is the only instruction file; when both exist,AGENTS.mdwins. In a file that already holds the section, the text between the markers is replaced with the current one and reportedupdated, orunchangedwhen it already matches. - Each client's MCP config. The
verbatraserver entry from Connect an MCP client,npx -y @verbatra/mcpwithout--allow-spendorVERBATRA_, so the spend tools stay hidden.MCP_ ALLOW_ SPEND @verbatra/cliexports these entries asAGENT_.CLIENT_ CONFIGS
| Client | --client | File | Detected from |
|---|---|---|---|
| Claude Code | claude | .mcp.json | CLAUDE.md, .claude/, .mcp.json |
| Cursor | cursor | .cursor/mcp.json | .cursor/, .cursorrules |
| VS Code | vscode | .vscode/mcp.json, under servers | .vscode/mcp.json |
| Codex | codex | .codex/config.toml, as [mcp_ | .codex/ |
| Gemini CLI | gemini | .gemini/settings.json | .gemini/, GEMINI.md |
init wires every client it detects, and only Claude Code when it finds no marker. AGENTS.md picks the instruction file, not a client. A .vscode folder without mcp.json is not wired: a next step names the command that wires it. Gemini CLI reads GEMINI.md: unless its settings or GEMINI.md load the instruction file, a next step says how. A detected client whose config path runs through a symbolic link is skipped with reason symlink and a next step. --client replaces the detection; an unknown id, an empty list or --client without --agent exits 2 with INVALID_ and the candidates.
Nothing outside the markers is touched, and other servers and keys keep their bytes, indentation and line endings when the file is laid out one entry per line; otherwise it is rewritten with the same content. The Codex table is appended. An existing verbatra server is never touched: the file is reported unchanged, and the client's server is present when the entry matches the scaffold, or differs when it does not, which also adds a nextSteps note.
When .claude/settings.json or .claude/settings.local.json enables the verbatra Claude Code plugin, init skips .mcp.json: Claude Code is reported skipped with reason plugin and a next step, and an existing entry is never deleted. A settings file behind a symbolic link is ignored.
With an existing config, init --agent keeps it, writes only the agent files, prints kept verbatra.config.ts, never prompts and detects nothing. The JSON result lists the config as unchanged, sets agent.configKept to true, and carries null for config, sources, apiKeyEnvVar and detection. A config flag or --force puts the config back into the run, as without --agent.
init refuses before writing anything, with AGENT_, when a client file is not plain JSON (it never rewrites comments or trailing commas), not an object, repeats a key, or has a servers value that is not an object; when .codex/config.toml sets mcp_ or the verbatra server inline, with dotted keys or as an array of tables, repeats that table or a key in it, or has an unterminated string, array or header; when a client that --client names has a symbolic link on its config path, or an instruction file links outside the project; or when the instruction file has an unpaired, misordered or repeated marker. The message never quotes the file.
Exit codes
| Code | Meaning |
|---|---|
0 | files written, or already up to date |
2 | a usage error: MISSING_, INVALID_, INVALID_, INVALID_, INVALID_, INVALID_, FORMAT_, LAYOUT_, CONFIG_, INIT_, AGENT_, or CONFIG_ when the answers would not make a valid config |
See the exit-code contract for how every command uses these codes.
Related
- Set up verbatra with an AI agent is the agent prompt built on this command.
- Quickstart walks from init to the first run.
- The config file documents every key the scaffold sets.
- Providers covers the provider options and API key variables.