polycast-cli
v0.15.0
Published
Command-line client for Polycast — OTA localization for iOS
Downloads
60
Maintainers
Readme
polycast-cli
The polycast CLI is the command-line client for Polycast — OTA
localization for iOS. It manages projects, languages, and publishing from
your terminal and CI. Your app's day-to-day string syncing is handled
automatically by the Xcode build plugin; the CLI is for setup, capturing
everything on screen, and shipping translations live.
Requires Node 18+.
npm install -g polycast-cli # provides the `polycast` command
# or run without installing: npx polycast-cli <command>Quickstart (60 seconds)
polycast login # sign in (browser flow, or --token to paste a PAT)
polycast init # detect catalog + Xcode project, create the project, write polycast.json
polycast langs add ja es de # enable languages — AI drafting starts, with live progress
polycast coverage # capture every on-screen string, then draft it
polycast sync # push · publish --wait · pull — translations go liveAdd the two integration touches init prints, build once so the plugin
pushes your strings, and your app is live in four languages.
The two everyday commands
Almost all daily work is these two, in this order:
polycast coverage — get to full coverage. It plants the
Text → PText shadow into any module missing it, discovers your custom
display components in rounds (registering them so the scanner sees them),
then runs an LLM pass over your source to catch what static parsing
structurally can't — computed properties, enum cases, strings composed at
runtime. Everything it finds is captured into the catalog and pushed, so
the new strings start drafting. It repeats until a round finds nothing new.
(converge is an alias — same command.)
polycast coverage # capture everything, then draft it
polycast coverage --no-cache # force the LLM to re-read EVERY file (not just changed ones)
polycast coverage --no-llm # scanner rounds only, skip the LLM passThe LLM pass caches by file content, so unchanged files cost nothing on the
next run. --no-cache bypasses that cache and re-reads every file — and, in
coverage, surfaces an extraction failure instead of silently skipping it,
so you're never told "fully covered" when the LLM pass didn't actually run.
polycast sync — ship it. Runs push · publish --wait · pull end to
end: pushes source strings, waits (with a live per-language dashboard) for
drafting to finish, publishes drafts to the live CDN bundles, then pulls the
published translations back so your bundled fallbacks stay fresh.
polycast sync # push · publish --wait · pull
polycast sync --fix # also triage + apply runtime misses around the loopSo a normal change is just: polycast coverage && polycast sync.
Authentication — two credentials, on purpose
| Credential | Prefix | Used by | Where it lives |
|---|---|---|---|
| Admin token / PAT | pcadm_ / pcat_ | login, init, langs, coverage, publish, status, sync | OS keychain (macOS) / ~/.config/polycast/token 0600 |
| Project key | pc_live_ / pc_test_ | push, pull, build plugin | polycast.json in your repo; safe for CI |
The project key can only submit source strings and read published
translations (which your app ships publicly anyway). The admin credential
can enable languages, edit, and publish — it never goes in files or CI.
POLYCAST_ADMIN_TOKEN / POLYCAST_PROJECT_KEY env vars override for
headless use.
polycast.json
init writes just what your project needs:
{
"projectKey": "pc_live_8f2k...",
"catalogPath": "App/Localizable.xcstrings"
}The hosted endpoints are built into the CLI, the build plugin, and the SDK,
so they never land in your repo. init also gitignores .polycast/ (local
push state, shared byte-for-byte with the Swift build plugin — digests are
identical, so the CLI and plugin never re-push each other's work).
Self-hosting Polycast? Add an
"endpoint"field pointing at your API and the CLI and build plugin use it instead of the default.
Command reference
Everyday
coverage(aliasconverge) — capture every on-screen string in rounds, then draft it.--no-cachere-reads every source file (and fails loud on an extraction error);--no-llmskips the LLM pass.sync— the whole ship loop:push · publish --wait · pull.--fixtriages runtime misses around it.status— the project at a glance: coverage %, drafts pending, misses, dead-lettered jobs. Exit 1 on dead letters (CI health gate).--json.
Setup
login— sign in and store the admin credential (browser flow, or--tokento paste a PAT).logoutclears it.whoamishows orgs/roles.init— detect catalog + Xcode project, create the project + key, writepolycast.json. Idempotent: re-running verifies instead of recreating. Flags:--name,--catalog,--endpoint,--yes.plugin— attach the build plugin to your targets (Package.swift or pbxproj).shadow— plant theText → PTextshadow into source modules.
Languages
langs— the coverage table for every enabled language.langs add ja ko th/langs add all(App Store locale set) — enable languages; AI drafting starts immediately and is shown live per language.--no-waitreturns at once (drafting continues in the background);--timeout <minutes>raises the wait ceiling.langs remove th— disable languages (their bundles stop on next publish).
Strings
push— push new/changed source strings (what the plugin does on every build). Malformed / junk keys are skipped automatically.pull— merge published translations into the catalog so binary fallbacks stay fresh.--locales ja,esto limit;--checkexits 1 if the catalog is stale (CI guard). Never touches the source language.publish [locales...]— promote drafts to live bundles, with a live per-language dashboard.--waitwatches drafting finish first;--timeout <minutes>raises the wait ceiling. Needs-review strings stay unpublished and are called out.prune [--apply]— list, then delete, junk catalog keys (empty artifacts, dangling fragments, corrupt printf formats). Dry-run by default.gen --output <path>— type-safe Swift key constants; use withPText(L.paywall.title).
Health
doctor— every local + remote check with a one-line fix each: catalog parses · polycast.json valid · plugin attached ·.polycastgitignored · project key authenticates · admin token valid · edge reachable · ETag/304.lint— every literal accounted for, or the build fails — the all-text gate.--json.extract [--apply]— the LLM source pass on its own (whatcoverageruns).--no-cachere-reads every file;--no-depthruns a single pass.suggest [--apply]— find display components missing fromscanCalls.misses— runtime miss telemetry: keys devices requested that the bundle couldn't serve.--fix [--apply]triages them;--clearwipes rows.audit [--locale en-XA]— walk screens in the pseudo-locale and report any untranslated text on screen.
All commands: --json where output is data, no prompts when CI=true or
--yes. Exit codes: 0 success · 1 actionable failure · 2 usage error.
CI recipes
# bake fresh fallbacks into every release, before xcodebuild archive
- run: npx polycast pull
env:
POLYCAST_PROJECT_KEY: ${{ secrets.POLYCAST_PROJECT_KEY }}
# or fail the build if fallbacks are stale
- run: npx polycast pull --checkOnly the project key ever goes in CI secrets; if polycast.json is
committed, no secret is needed at all.
Troubleshooting
- New on-screen text isn't getting translated → run
polycast coverage(the LLM pass catches computed/composed strings the plugin's lexer can't see), thenpolycast sync.polycast coverage --no-cacheforces a full re-read. - No strings after building →
polycast doctor; usual cause is the plugin missing from the target or a sandboxed network push (workaround:polycast push). - Translations not showing in the app → drafts aren't live until
polycast publish(orsync); devices refresh on foreground. - A translation looks wrong → edit via the admin API/dashboard grid (becomes human-locked), publish. Live in minutes.
- 401 → admin commands need
polycast login; push/pull need a validpolycast.json. Doctor says which.
