@sonenta/cli
Command-line interface for Sonenta translation
management. Sits next to the API and the React SDK; calls into the public
HTTP surface using a Sonenta API key.
Install
npm install -g @sonenta/cli
npx @sonenta/cli --help
Quick start
sonenta login --host https://api.sonenta.dev --token snt_live_<prefix>.<secret>
sonenta init --project 069fc15d-… --version main
sonenta import locales/fr/common.json
Configuration
Two files are involved:
sonenta.config.json: per-project, committed to the repo.
Contains the host + project_uuid + version_slug. Any command resolves
it by walking up from the current directory.
sonenta init also writes an idempotent, marker-delimited Sonenta managed
block into CLAUDE.md and AGENTS.md at the repo root, so any coding agent
that reads those files knows the project id, source language, namespaces, CDN
host, how to edit/publish translations, and how to add the @sonenta/mcp MCP
server. Re-running sonenta init --force refreshes the block in place (your own
content outside the markers is never touched); when you are logged in it is
populated live from GET /v1/me + project info. Pass --no-repo-doc to skip it.
~/.sonenta/credentials: per-user, never committed.
JSON file with mode 0600 keyed by host so the same user can hold
credentials for multiple Sonenta deployments simultaneously
(e.g. cloud + local dev + self-hosted prod). The currently active
host is recorded as default.
The credentials file shape:
{
"default": "https://api.sonenta.dev",
"hosts": {
"https://api.sonenta.dev": { "api_key": "snt_live_…", "user_email": "…" },
"https://api.dev.sonenta.ca": { "api_key": "snt_live_…" }
}
}
Commands
All project commands talk to the metered MCP surface
(/v1/mcp/projects/{id}/…) and require an API key carrying the mcp:*
scope (see Auth).
login / logout | Store / remove an API key for a host |
whoami | Show the active host + masked key |
init | Scaffold sonenta.config.json + write a managed Sonenta block into CLAUDE.md / AGENTS.md |
projects list | List projects the key can reach |
keys list | List keys (namespace_slug/key_name) |
import <files…> | One-shot import of i18next JSON (nested or flat), creates keys + upserts translations |
push | Push the whole local locales/ tree, BATCHED (each key keeps all its languages together, source never split from targets); --batch-size |
pull | Pull translations into locales/<lang>/<namespace>.json |
export | Export translations as i18next JSON (flat, or --nested) |
status | Diff local locales/ against the remote project |
releases publish | Trigger a CDN release (publish bundles) |
snapshot | Emit a build-time initialBundles module for @sonenta/react-i18next |
missing | List runtime-detected missing keys |
a11y scan | Read your source, infer each key's semantic type from how it is USED, and set it (type_source=detected) |
agents list | List the agents available to install (server catalogue; needs a network) |
agents add <name> | Download an agent into .claude/agents/<name>.md + auto-wire .mcp.json + run a preflight (credentials + paid plan) |
doctor | Preflight the agent setup: MCP wired + reachable, key has mcp:*, project a11y tools respond, with an exact fix for anything off |
Mutating commands accept --dry-run and print a created / updated / unchanged
summary.
sonenta a11y scan
Every key carries a semantic type (one of 17: button, link, image,
heading, input_placeholder, …). That type decides which accessibility
treatments the key offers, so a project where everything is still the default
text reports a false a11y coverage on every screen.
Nothing in the string itself can tell a button label from a paragraph. Only the
code can, so this reads it:
sonenta a11y scan --dry-run
sonenta a11y scan
sonenta a11y scan --offline
It parses your i18n usage sites (t('key'), $t, <Trans>, data-i18n, in
.tsx/.jsx/.vue/.svelte/.astro/.html), reads the markup around each one, and
infers the type: a call inside <button> is a button, an alt= is an
image, a <nav> list item is a menu_item, toast.success(t(…)) is a
toast. Then it sets the types with type_source=detected and records the
scan (keys_scanned, keys_classified, tool + version), which is what lets the
Accessibility screen say when the analysis last ran.
Local analysis, no AI, 0 credit.
Three behaviours worth knowing:
- A key you typed by hand is never overwritten. A
manual type is a human
correction; the scan reports it as skipped (manual) and moves on.
- It refuses to guess. A key used as a button in one place and a heading in
another is reported as a conflict and left alone, as is a key with no usable
context. Better an honest gap than a wrong type.
- Evidence is graded.
--min-confidence (default medium) sets the floor:
high = the markup states it, medium = a naming convention, low = only
the key name hints at it. Run with --min-confidence low to include key-name
hints, --format json to audit which rule fired on every key.
sonenta doctor
sonenta doctor (also run automatically at the end of sonenta agents add)
checks, in order: project config, login, the .mcp.json wiring, the host is
reachable (no silent 404), the account is active, the key carries the mcp:*
scope, and the project's a11y MCP tools respond. Every failure prints the exact
next step (e.g. "key lacks mcp:* → create one in Org Settings → API Keys, then
sonenta login"), and it exits non-zero so it gates CI. The one thing it can't
observe (whether your Claude session has loaded the MCP connection) it reminds
you to fix by reloading the session.
Installable agents
agents add downloads an agent definition from the server into the project's
.claude/agents/ directory, usable interactively in Claude Code or headless in
CI. It needs credentials and a paid plan, and it needs a network: there is no
bundled copy to fall back on.
The set is served by the API, not listed here: descriptions live on the server so
there is one copy of them, and this file cannot drift from it. The agents' own
instructions are not distributed in this package and are fetched at install time
for accounts whose plan includes them.
To see what exists, with live descriptions in your language:
sonenta agents list
sonenta agents add sonenta-a11y
sonenta agents add sonenta-i18n
sonenta agents add sonenta-source-health
sonenta agents add sonenta-knowledge
sonenta agents add sonenta-surface
The agents reach these tools through the
@sonenta/mcp server, so configure
that server with an mcp:* key first. --dir <path> targets another project
directory; --force overwrites an existing definition.
Sync workflow
sonenta import locales/fr/common.json
sonenta import --namespace common fr.json
sonenta push --dry-run
sonenta push
sonenta pull --language fr
sonenta status
sonenta releases publish
sonenta snapshot --out src/sonenta-bundles.ts
Auth
Every call emits Authorization: ApiKey <prefix>.<secret> (capital A, lowercase
k) against the MCP surface, so the API key must carry the mcp:* scope.
Generate one in the dashboard at Org Settings → API Keys. The key is supplied
by you, via sonenta login, the SONENTA_TOKEN env var (handy for CI), or
~/.sonenta/credentials, and is never hard-coded.
sonenta login validates before storing. It checks the key against
GET /v1/me and only stores it when the key is valid and the account is
active, an invalid key or an inactive account is rejected with a clear message
and nothing is written.
Value commands require an active login. agents add, import, push,
pull, export, status, releases publish, snapshot, missing,
keys list, and projects list verify you're logged in with an active account
(via /v1/me) before they act, otherwise you get
Not logged in. Run \sonenta login`orAccount inactive …. The local commands login, logout, whoami, init, and agents list` are not gated.
Credentials live in ~/.sonenta/credentials (mode 0600); the legacy
~/.verbumia/credentials is still read as a fallback, and the next write
migrates you to ~/.sonenta.
0.2.0 migration (breaking): the whole CLI moved from the project REST
surface (/v1/projects/…, project-scoped keys) to the MCP surface
(/v1/mcp/projects/…). Re-issue your CLI key with the mcp:* scope:
project-scoped keys now return 403.
License
MIT