Skip to content

CLI Reference

The @palamedes/cli package publishes pmds. Its npm launcher selects the installed platform binary; the native Rust executable owns built-in and plugin command dispatch.

Page outline

Plugin Commands

Plugins register a namespace plus commands. They are invoked as:

pmds <plugin> <command> [...args]
pmds <plugin> <command> --json [...args]
pmds <plugin> <command> --config ./palamedes.yaml [...args]
pmds <plugin> <command> --refresh-plugin-manifests [...args]

--json, --config, -c, --plugin-timeout-ms, and --refresh-plugin-manifests are reserved host options after the command; put -- before them to pass them through as plugin arguments. Unknown namespaces produce the native plugin host's unknown-namespace diagnostic.

Plugin loading is explicit, binary-only, and applies only to non-built-in namespaces. Validated manifests are cached under .palamedes; changed binaries are described again, while cache failures fall back to the normal handshake. --refresh-plugin-manifests ignores the cached entries once and replaces them after describing every configured plugin again. See the configuration field and the binary plugin protocol.

pmds extract

Extracts messages from configured source files and writes source-string-first catalogs.

pmds extract
pmds extract --config ./palamedes.yaml
pmds extract --clean
pmds extract --force-clean
pmds extract --check
pmds extract --check --json
pmds extract --fail-on-empty-catalog
pmds extract --watch
pmds extract --verbose

Options:

OptionDescription
-c, --config <path>Use a specific config file.
-w, --watchRe-run extraction on file changes (debounced). Fatal authoring errors are printed and watching continues; the config file is watched and reloaded on change.
--cleanRemove obsolete entries with obsolete-since at least 30 days old; keep undated obsolete entries.
--force-cleanRemove all obsolete entries immediately, including undated entries.
--checkExit unsuccessfully when extraction would create or modify a catalog, without writing catalog files. Cannot be combined with --watch.
--jsonWith --check, print one deterministic result document.
--fail-on-empty-catalogFail an extraction cycle before any catalog update when a configured catalog matches no source files.
--threads <COUNT>Worker threads for the parallel extraction pass. Overrides extract-threads in the config; defaults to 4; 1 runs serial.
--no-cacheIgnore and do not write the extraction cache in .palamedes/. Use for a cold run; the cache is on by default.
-v, --verbosePrint verbose extraction details.

Watch mode resolves project and include-directory symlinks to the filesystem targets observed by the native watcher while continuing to apply include and exclude patterns in their configured path space. A project reached through a symlink and an included directory that is itself a symlink therefore receive the same extraction updates as their one-shot equivalents.

--check runs the normal source discovery, extraction, catalog projection, obsolete-entry policy, metadata generation, and PO/FCL serialization in memory. It compares the exact resulting bytes with each configured catalog. Catalog files, missing catalog directories, and catalog modification times remain unchanged. The source-analysis cache may still be populated; add --no-cache when the entire check must avoid cache writes.

Use --fail-on-empty-catalog in automated extraction pipelines that must treat an empty source match as a configuration failure. Catalog projection starts only after source discovery has succeeded, so one empty catalog exits with code 1 before any catalog is written or marked obsolete. Without the flag, the existing behavior remains: Palamedes warns and projects that catalog from an empty message set. In --check --json mode the failure is returned as status error with an empty catalogs array. Watch mode prints the failure, leaves every catalog unchanged for that cycle, and keeps watching so a later source change can recover.

--clean and --force-clean keep their normal meaning in check mode. If both are present, --force-clean wins. Because regular extraction does not delete catalog files, the current catalog change kinds are created and modified; cleanup flags remove obsolete entries inside the projected file.

JSON paths are relative to the configuration root when possible, use / as the separator, and are sorted deterministically:

{
  "status": "drift",
  "catalogs": [
    { "path": "catalogs/de/messages.fcl", "change": "modified" },
    { "path": "locales/de/messages.po", "change": "created" }
  ]
}

A clean result uses { "status": "clean", "catalogs": [] }. An extraction or configuration failure uses status error, an empty catalogs array, and error.message. Exit code 0 means clean, 1 means the check could not run, 2 is reserved by Clap for invalid command-line usage, and 3 means catalog drift. A minimal CI check is:

pnpm exec pmds extract --check --json --fail-on-empty-catalog

pmds lint

Checks Palamedes source authoring without creating or updating catalogs. It uses the union of the configured catalogs[].include and exclude file sets, deduplicates overlapping catalogs, and supports JS, TS, JSX, TSX, and MDX.

pmds lint
pmds lint --json
pmds lint --fail-on warning

Options:

OptionDescription
-c, --config <path>Use a specific config file.
--jsonPrint one deterministic result document.
--fail-on <level>Fail on error or warning. Default: error.
--threads <COUNT>Worker threads for bounded parallel source analysis. Overrides extract-threads; defaults to 4; 1 runs serial.
--no-cacheIgnore and do not write the shared source-analysis cache.

Human diagnostics contain file, line, column, severity, stable code, message, and actionable help. JSON contains diagnostics, failedFiles, and a summary with file and severity counts plus the number of suppressed findings. Diagnostics are ordered by file, byte range, and code; failed files are ordered by file. The document shape is stable:

{
  "diagnostics": [
    {
      "code": "pmds/no-placeholder-only-message",
      "severity": "warning",
      "file": "src/view.tsx",
      "primary": { "start": 91, "end": 103, "line": 3, "column": 14 },
      "message": "This message contains placeholders but no translatable text.",
      "help": "Move translation to the surrounding authored sentence, or remove the translation macro if this value should be rendered as-is."
    }
  ],
  "failedFiles": [],
  "summary": {
    "files": 1,
    "errors": 0,
    "warnings": 1,
    "infos": 0,
    "suppressed": 0,
    "failedFiles": 0
  }
}

Exit code 0 means the configured threshold passed. Exit code 4 means lint completed and either diagnostics met the configured --fail-on threshold (whether errors alone or errors and warnings) or one or more source files could not be analyzed. Exit code 1 means configuration, I/O, or output execution failed before lint could produce its result. Exit code 2 remains reserved by Clap for invalid command-line usage. This mirrors extract --check: a completed verdict has a dedicated code that CI can distinguish from a command that could not run.

Suppressions are deliberately code-specific and line-scoped. A directive must start immediately after a supported comment opener, allowing whitespace but no other text; in block and HTML comments, that whitespace may continue onto the directive's physical line. A disable-next-line directive in a multi-line block or HTML comment applies to the first line after that comment closes. JS and TS accept // and /* ... */; JSX and TSX accept those forms including JSX {/* ... */}; MDX accepts HTML <!-- ... --> and JSX {/* ... */}; JavaScript expressions in MDX also accept their native // and /* ... */ forms. Raw MDX template text and fenced code blocks are always ignored so documentation examples cannot become unused suppressions.

// palamedes-lint-disable-next-line pmds/no-placeholder-only-message
const label = t`${status}`;

const inline = t`${status}`; // palamedes-lint-disable-line pmds/no-placeholder-only-message

Unknown codes, directives without a code, and valid suppressions that no longer match a finding are reported by pmds lint.

palamedes-lint-disable-* directives are consumed by pmds lint only. The ESLint and Oxlint adapter in @palamedes/eslint-plugin leaves suppression handling to its host, so the same finding is silenced with eslint-disable-next-line palamedes/<rule> or oxlint-disable-next-line palamedes/<rule> there. The two forms do not transfer: a project running both lanes needs the directive each lane understands. The rule names differ too — the CLI reports the native diagnostic codes (pmds/...), while the adapter exposes them under its host plugin namespace (palamedes/...). The default .palamedes/extract-cache.json stores compatible messages and diagnostics together, so extract and lint can reuse the same native parse. Lint follows extraction's bounded parallel read/parse model. Workers only observe the immutable cache; results are suppressed, merged, and inserted into the cache serially in source-file order, so cold, warm, and repeated runs keep the same diagnostic output. A failed cache write remains non-fatal but is always reported on stderr, including without --verbose.

pmds audit

Audits catalogs for missing translations, fuzzy review markers, and ICU authoring issues across PO and FCL.

pmds audit
pmds audit --locale de fr
pmds audit --locale de,fr
pmds audit --json
pmds audit --fail-on warning
pmds audit --fail-on info

Options:

OptionDescription
-c, --config <path>Use a specific config file.
--locale <locale...>Audit only selected target locales. Space-separated and comma-separated values are accepted.
--jsonPrint the machine-readable audit result.
--fail-on <level>Fail on error, warning, or info. Default: error.

--fail-on info makes informational diagnostics such as catalog.fuzzy_flag fail the command, which is useful when CI must reject every catalog entry that still carries a review marker. The default remains --fail-on error. Exit code 5 means the audit completed and the configured policy threshold failed.

pmds report

Reports per-locale translation completeness from configured catalogs. Source locale entries count as translated; target locales are compared against the source catalog messages that are not obsolete. PO and FCL entries marked fuzzy need review and count as untranslated (reported in a separate fuzzy column).

pmds report
pmds report --locale de fr
pmds report --locale de,fr --json
pmds report --fail-if-below 95

Options:

OptionDescription
-c, --config <path>Use a specific config file.
--locale <locale...>Report only selected target locales. Space-separated and comma-separated values work.
--jsonPrint the machine-readable completeness report.
--fail-if-below <percent>Fail when any reported locale is below this translated percentage.

Exit code 6 means the report completed and at least one reported locale was below --fail-if-below.

Exit codes

The CI-facing commands use stable, distinct exit codes so a policy verdict is not confused with an operational failure:

CodeMeaning
0The command completed and its configured policy passed.
1The command could not complete because of configuration, I/O, serialization, or another operational failure.
2Clap rejected invalid command-line usage.
3extract --check completed and found catalog drift.
4lint completed and its --fail-on policy failed, or source analysis could not complete for one or more files.
5audit completed and its --fail-on policy failed.
6report completed and one or more locales were below --fail-if-below.

pmds catalog merge

Merges catalog files with Palamedes catalog semantics. This is suitable for Git merge-driver workflows.

pmds catalog merge ours.po theirs.po --output merged.po
pmds catalog merge ours.po theirs.po --base base.po --output merged.po
pmds catalog merge-driver %O %A %B %A --path %P --conflict-strategy use-first
pmds catalog merge ours.fcl theirs.fcl --output merged.fcl

pmds catalog merge requires exactly two current input catalogs. Without --base, they are combined in precedence order. With --base, the command performs a deletion-aware three-way merge with explicit ancestor, ours (first input), and theirs (second input) roles:

  • an entry absent from both current sides stays deleted;
  • a deletion wins when the other side is unchanged from the ancestor;
  • an entry modified on one side and deleted on the other follows --conflict-strategy and emits combine.modify_delete_resolved when resolved;
  • entries newly added on either side are retained; and
  • an entry changed on only one side is accepted without a translation conflict.

use-first selects ours and use-last selects theirs for translation and modify/delete conflicts. error rejects the merge. Parse errors and rejected conflicts leave the output file unchanged. PO and FCL use the same identity and deletion rules; identity is the source message plus optional gettext context.

Merged PO catalogs are written in the same order and shape as an extraction produces, so a resolved conflict does not land as a fully re-sorted file that the next pmds extract sorts back. Applying the catalog's own po options needs to know which configured catalog is being merged, and a Git merge driver only ever sees temporary files — pass %P through --path for that. Without it the output path is used, which is enough outside a merge driver.

Options:

OptionDescription
--output <path>Required output path.
-c, --config <path>Use a specific config file when inferring source-locale.
--format <format>po or fcl. Overrides path inference when supplied.
--base <path>Optional common ancestor for a three-way merge.
--conflict-strategy <strategy>use-first, use-last, or error. Default: use-first.
--source-locale <locale>Source locale for catalog semantics. Defaults to config or en.
--locale <locale>Locale of the merged catalog.
--path <path>Real catalog pathname; pass %P in a Git merge driver.

Git merge driver

Use catalog merge-driver for Git instead of reconstructing role handling in a wrapper script:

git config merge.palamedes-catalog.driver \
  'pmds catalog merge-driver %O %A %B %A --path %P --conflict-strategy use-first'

During a normal merge, Git's %A is logical ours and %B is theirs. During a rebase, Git internally assigns the upstream side to %A and the commit being replayed to %B. merge-driver detects the active rebase and swaps those inputs before calling the Core API, so use-first consistently means “the branch being merged or rebased wins.” Use --operation merge or --operation rebase to override auto-detection in unusual Git orchestration.

The positional arguments are ancestor (%O), Git current file (%A), Git other file (%B), and output (%A). --path %P selects the real configured catalog so its PO formatting options are applied to the output and, when --format is omitted, determines whether the extensionless Git temp files are PO or FCL. This one driver supports mixed PO/FCL repositories. Outside a merge driver, omit --path and format inference continues to use the input/output catalog paths.

The driver merges even where no data config is discoverable — including projects configured through palamedes.config.ts — because a registered driver runs wherever the conflicted worktree is. Configured PO output options and source-locale are then not applied, and the driver says so on stderr.

pmds catalog convert

Converts supported PO catalogs to Ferrocat Catalog Lines (FCL). Translator comments, obsolete state, and review markers such as fuzzy are preserved. The output file is replaced only after conversion succeeds.

pmds catalog convert src/locales/de.po --to fcl --output src/locales/de.fcl
pmds catalog convert src/locales/de.po --to fcl --locale de
pmds catalog convert --config palamedes.yaml --to fcl

Config mode writes .fcl files beside existing .po files and leaves the source catalogs untouched. It derives each output through the target-format resolver, so an explicitly configured messages.po path becomes messages.po.fcl and will resolve after changing only format. Update the catalog config afterwards:

catalogs:
  - path: src/locales/{locale}
    format: fcl
    include: [src]

See Catalog formats for when to keep PO storage and when to opt into FCL.

Options:

OptionDescription
<input>Optional input catalog for single-file conversion.
-c, --config <path>Convert configured PO catalogs. Cannot be combined with --output.
--to <format>Target format. Currently fcl.
--output <path>Output path for single-file conversion. Defaults to the input path with a .fcl extension.
--source-locale <locale>Source locale for single-file conversion. Default: en.
--locale <locale>Locale for single-file conversion.

pmds version

Prints the installed CLI version. pmds --version / pmds -V print the same information.

pmds version
pmds --version

Advisory update checks

The release workflow configures the native CLI's privacy-bounded update check for the next release, following completed service readiness. The consuming release and its production-check evidence remain pending in #1036 and version-service #12; configuration alone does not mean that an enabled release has shipped. Ordinary/local builds without PALAMEDES_UPDATE_ENDPOINT make no update-check request and write no update-check cache. A configured build fails unless the value is exactly the owned HTTPS route https://version-service.sebastian-software.de/check; malformed, alternate-host, credentialed, port-qualified, query, and fragment values cannot produce a release binary. The server half of the contract lives in the public version-service repository and serves all Sebastian Software CLIs.

Once enabled, a regular pmds subcommand checks at most once per 24 hours. It runs as part of that process — never as a daemon, background service, or postinstall hook — and sends only this JSON shape over HTTPS:

{
  "project": "palamedes",
  "version": "<installed pmds version>",
  "os": "linux",
  "arch": "x86_64",
  "ci": false,
  "installedSince": "2026-08"
}

installedSince is a coarse year-month install cohort and is deliberately never finer than a month: combined with the other fields, a day-precision value could form singleton combinations whose daily requests become linkable — a de-facto identifier. A month reduces specificity but does not guarantee that every combination is nonunique or unlinkable. There is no installation or telemetry ID and no command, arguments, path, project data, username, or hostname. Palamedes application code does not access or persist client IP addresses, forwarded headers, user agents, or other identifying headers. The service reads the request URL path, Content-Type, and Content-Length only to validate the route, media type, and body-size limit, and forwards nothing but the documented aggregate dimensions — with neutralized transport metadata — to its analytics sink. It aggregates rate-limited request volume and version/OS/architecture/CI/cohort distributions; because it has no stable identifier, those requests are not unique weekly installations.

Set either opt-out before invoking the CLI to prevent both cache and network access:

export DO_NOT_TRACK=1
# or
export PALAMEDES_UPDATE_CHECK=0

The platform cache is under $XDG_CACHE_HOME/palamedes (falling back to $HOME/.cache/palamedes) on Linux, $HOME/Library/Caches/palamedes on macOS, and %LOCALAPPDATA%\palamedes on Windows. A due attempt is recorded before the request so offline use is not retried on every command, and the year-month cohort is persisted beside it as installed-since-v1; deleting the cache restarts the cohort. Cache and network failures are non-fatal and silent. The network operation has a two-second total deadline; a valid newer semantic version produces only this stderr notice after the command output:

A new version of palamedes is available: <installed version> → <published version>

Stdout and exit status are unchanged, including for --json commands. A silent check is not proof of successful ingestion, and a timeout can occur after the service accepted an event. The service's 10-minute registry cache can briefly return the previous version after publication; do not delete the client cache to poll. Packaging smoke tests opt out without disabling checks in the shipped artifact. Removing endpoint embedding only affects future builds; existing binaries require an opt-out or an update to a disabled build. See ADR-027 for the data boundary and the version-service repository for the service implementation and its deployment guide.

Configuration Errors

The native CLI reads only data configs (palamedes.yaml, .yml, .json, .toml). When the upward search finds only a JavaScript or TypeScript config, the CLI reports that specifically instead of a generic not-found error — create a data config next to it for CLI use. Known keys written in camelCase (sourceLocale, pseudoLocale, fallbackLocales, sourceReferenceRoot, referenceScopes, extractThreads, extractCache) are rejected with a kebab-case hint instead of being silently ignored; other unknown top-level keys produce a warning. Fallback-locale entries must reference configured locales, and a pseudo-locale outside locales warns that it will be ignored.