Reference

CLI reference

The pharen command-line tool is the thin, authenticated client behind distribution and symbol upload. The fastlane plugin and the Xcode run script wrap it — this page is the underlying surface.

pharen setup ios

The whole on-ramp as one command: sign in, write the project files, mint your ingest keys, wire the project, verify. What's left afterwards is one line of your own Swift.

bash
pharen setup ios      # sign in, configure, wire, verify
# ── call Pharen.start(), build and run the app ──
pharen verify --wait  # confirm your first event arrived

setup doesn't watch for your first event, because on a first run nothing can have arrived — you still have to build. The confirmation is its own command, whose name is what it does, and setup prints it at the moment it becomes useful, so there is nothing to remember. (setup --wait exists as sugar when you already have the app running and want one command end to end.)

In the ordinary case it takes no arguments at all. Your org is derived from your credential, and your app id is proposed from the project's own bundle identifier (case-folded, so com.Acme.MyApp and com.acme.myapp are the same app) and confirmed with you before it is used. So there is nothing to look up and nothing to get wrong. --app overrides the proposal; if you pass --org and it disagrees with your credential, that's a hard stop rather than a silent switch to the wrong tenant.

It is stateless and idempotent: every step declines to redo work that is already done, so re-running always resumes and never repeats. That is why “run it again when you've wired it up” is safe, and why a re-run can't issue a second set of keys.

Wiring your project

Where your project is built from a project.yml (XcodeGen), setupalso points it at the generated config, adds the SDK package, and declares PharenIngestKey in your Info.plist. It prints every edit — file by file, line by line — and asks before writing any of them. You know whether your working tree is clean and whether now is the moment; the tool doesn't.

-y applies without asking (for CI and scripted runs) and --no-wire skips the step. With no terminal and no -y the answer is no, the plan is still printed, and the run continues — it never blocks waiting on input that can't arrive. Declining is a correct answer, not a failure: your keys are minted either way and verify reports what is still unwired.

Edits are additive only. Where anything is ambiguous — an existing configFiles: block pointing elsewhere, an inline include: [...], a hand-managed .xcodeproj — it declines and tells you what to do by hand rather than guessing at your project file. The SDK is linked to your app target only, never to App Clips or extensions.

FlagRequiredDescription
--apprequiredThe app id. The only input; org comes from your credential.
-y, --yesoptionalApply the project edits without asking. For CI and scripted runs.
--no-wireoptionalNever touch project.yml or Info.plist; just report what is unwired.
--waitoptionalAlso watch for the first event once configuration finishes. Off by default.
--timeoutoptionalSeconds to wait for that first event. Default: 180.
--no-loginoptionalNever start the browser sign-in; fail fast if there is no credential. For CI.
--environmentoptionalRepeatable; mint only these environments instead of all three.
--api-base-urloptionalDevelopment override written into the generated xcconfig (escaped for you).
--orgoptionalAssert your tenant. Derived when omitted; a mismatch is a hard stop.
--team-id / --dir / --force / --no-laneoptionalPassed through to init ios.

What the CLI touches on your machine

setup and verify read your source and edit your project files, so here is the whole of it — the answer you would otherwise have to get by reading the code.

What it reads

To diagnose an integration, verify parses your project.yml, every Info.plist under your app directory, and every .swift file there (looking for a Pharen.start call). None of it is transmitted. Those reads feed a regular expression and a substring test inside the process and are then discarded. Across a whole run — --wait included — the CLI makes three requests, all GET, all to the API host you configured:

text
GET /v1/auth/session                    does this credential still authenticate
GET /v1/ingest-keys?app_id=…            are the keys in your build still live
GET /v1/events?app_id=&environment=…    has anything arrived yet   (--wait only)

No request body, no file contents, no file paths, no source. That isn't a promise in a document: the test suite runs the whole command over a tree whose files carry distinctive markers and fails if any marker turns up in an outgoing URL, header or body. The assertion exists to break if anyone ever adds telemetry to a check.

The directory walk is bounded to your app directory and never follows a symlink — not into a directory, not to a file whose name happens to match. It also stops at a depth and entry budget, and reports when it did, so a partial answer never reads as a clean bill of health.

What it writes

  • .pharen.yml, release/ and Config/Pharen.xcconfig — files Pharen owns. It refuses to overwrite an existing one unless you pass --force.
  • project.yml and your Info.plist — files you own. Only additions, only after it prints every line and you say yes, and never where the situation is ambiguous. This is the step -y and --no-wire control.
  • ~/.config/pharen/credentials.json — your session from pharen auth login, written 0600 in a 0700 directory. Nothing else ever writes a credential to disk, and pharen auth logout deletes it.

Configuration

Three inputs configure every command. In precedence order, highest first:

  1. Command-line flags--org, --app, --api-url.
  2. EnvironmentPHAREN_AUTH_TOKEN (the one secret) and PHAREN_API_URL (development only; see below).
  3. .pharen.yml — your committed org and app.
yaml.pharen.yml
org: your-org      # customer-facing alias of your tenant
app: your-app-id   # the app_id

.pharen.yml is found by walking up from the working directory — the nearest one wins — so it works whether a command runs from the repo root or an app subdirectory. It holds no secrets. Your --org never grants access on its own: the auth token's tenant is authoritative, and a token/config mismatch is a hard 403.

PHAREN_AUTH_TOKEN is a secret — set it in your CI environment, never commit it. Every command that talks to the control plane needs it (init ios is the exception — it's purely local, unless you pass --mint). Interactively, pharen auth login is the friendlier path: it signs you in from any browser and stores the session, so no token has to be handled at all. Output is JSON on stdout, so Fastlane and CI can parse it.

pharen init ios

Sets up a repo: writes .pharen.yml at the root and copies the release lane into your app. Local only — no network, no prompts. Re-running won't overwrite existing files unless you pass --force.

bash
pharen init ios --org your-org --app your-app-id

Add --mint and it also issues your ingest keys and writes them into your build settings — the whole on-ramp in one command:

bash
pharen init ios --org your-org --app your-app-id --mint

This mints one key per environment and generates a committed Config/Pharen.xcconfig that assigns them per build configuration (Debug → development, Staging → staging, Release → production), with a development-key fallback for any configuration it doesn't name — never the production key. It ends with #include? "Pharen.local.xcconfig", so a gitignored per-machine override still wins if you keep one. Everything else about init ios stays local.

--mint needs a credential. Either sign in first with pharen auth login, or add --login to run that flow as part of this command. It's opt-in rather than automatic because the sign-in blocks waiting for you to approve in a browser, and this command also runs in lanes — without the flag, a missing credential fails in a second and names what to run.

FlagRequiredDescription
--orgrequiredYour tenant alias.
--apprequiredThe app id.
--mintoptionalAlso issue ingest keys and write Config/Pharen.xcconfig. Needs a credential.
--loginoptionalWith --mint: run the browser sign-in inline instead of failing. Opt-in — it blocks on a human, and lanes run this command.
--environmentoptionalRepeatable; mint only these environments instead of all three.
--api-base-urloptionalDevelopment override written into the generated xcconfig (escaped for you).
--team-idoptionalApple team id for exportOptions.plist (derived from Xcode when omitted).
--diroptionalApp directory to write release/ into (auto-detected otherwise).
--forceoptionalOverwrite existing .pharen.yml / release files, and regenerate the xcconfig.
--no-laneoptionalWrite only .pharen.yml, skip the release lane.

pharen verify

Checks an integration end to end and tells you what is wrong. Every way an iOS integration can be misconfigured fails silently — the SDK is deliberately quiet when it isn't configured, so a broken setup looks exactly like a working one until you go looking for data that was never sent. This is where you find out instead.

bash
pharen verify          # check the setup
pharen verify --wait   # check it, then watch for your first event

It asserts, in order:

  • .pharen.yml resolves an org and app.
  • Your control credential authenticates — not merely that a token is set.
  • The generated build config exists, and each configuration has a well-formed key.
  • Ring alignment — Release carries a phi_live_… key, the others phi_test_…, and the unnamed-configuration fallback is never production.
  • No value was truncated by the xcconfig // comment rule.
  • The project actually reads the xcconfig, and some Info.plist declares PharenIngestKey. These two are best-effort file checks, so they warn and name what they looked at rather than failing your build.
  • The keys baked into your build are still live server-side, and none was revoked.
  • With --wait: your first event arrives — reported with the version and build it came from, so you know it was this build talking.

Each check reports pass, fail, warn, or skipped; a check that couldn't run says so rather than silently counting as a pass. Exit is non-zero on any failure, and the JSON on stdout carries the same list — so it works as a CI gate.

FlagRequiredDescription
--waitoptionalPoll until the first event arrives from this app.
--environmentoptionalWhich ring --wait watches. Default: development.
--timeoutoptionalSeconds to wait for the first event. Default: 180.
--diroptionalApp directory to inspect (auto-detected otherwise).
--org / --app / --api-urloptionalOverride the resolved config.

pharen releases new

Registers or updates a release — the anchor incoming crashes resolve against. Idempotent: re-running for the same version and build is a safe no-op.

bash
pharen releases new --platform ios --version 1.2.0 --build 42 --commit "$(git rev-parse HEAD)"
FlagRequiredDescription
--platformrequiredios | android | web.
--versionrequiredMarketing version (CFBundleShortVersionString on iOS).
--buildrequiredBuild number (CFBundleVersion on iOS).
--commitoptionalCommit sha, attached as provenance.
--commit-rangeoptionalCommit range for this release.
--finalizeoptionalMark the release as deployed.
--org / --app / --api-urloptionalOverride the resolved config.

pharen upload-build

Uploads the installable and returns the OTA install link. On iOS, --bundle-id is required — the server generates the install manifest and the device refuses a mismatch.

bash
pharen upload-build --platform ios --version 1.2.0 --build 42 \
  --bundle-id com.yourcompany.yourapp ./App.ipa
FlagRequiredDescription
<path>requiredPositional: path to the .ipa (or .apk).
--platformrequiredios | android.
--version / --buildrequiredRelease identity.
--bundle-idiOS requiredCFBundleIdentifier; used to generate the install manifest.
--titleoptionalInstall-dialog title (defaults to the app id).
--skip-latestoptionalLeave the stable "latest" pointer where it is.
--expiresoptionalTime-to-live for the build.
--org / --app / --api-urloptionalOverride the resolved config.

pharen upload-symbols

Uploads debug symbols, kept private under your tenant. Slim by design — a token and a path is a complete invocation. Platform is inferred from the artifact (a dSYM implies iOS), and the server deduplicates by debug id, so re-uploads are cheap no-ops that still exit 0.

bash
pharen upload-symbols ./App.dSYM
FlagRequiredDescription
<paths>requiredOne or more .dSYM bundles (or the DWARF binaries inside them).
--platformoptionalOverride the inferred platform.
--uuidoptionalDeclare debug ids explicitly (repeatable); otherwise read from the binary.
--typeoptionalauto | dsym.
--org / --app / --api-urloptionalOverride the resolved config. app is provenance only — symbols join by debug id.

pharen ingest-keys new

Mints a single ingest key for one environment — the phi_live_… / phi_test_… value behind PharenIngestKey. The secret is shown once, and this command hands it to you, so you place it yourself. For setting up an app, prefer pharen setup ios (or pharen init ios --mint when scripting), which mints every environment's key and writes them into your build settings for you. Reach for this one to add a ring later, or to replace a key.

bash
pharen ingest-keys new --environment production
FlagRequiredDescription
--environmentrequiredproduction | staging | development.
--platformoptionalScope the key to a platform.
--org / --app / --api-urloptionalOverride the resolved config.

Everything else

The commands above are the ones you meet while setting an app up and shipping it. The rest of the tool is listed here so you know it exists; each one documents itself in full with pharen help <command>.

CommandWhat it does
pharen appsWhich apps your tenant has, according to the platform — the list to check an app id against, since a typo otherwise produces a working command pointing at nothing. Archived apps are listed by default (an id is never reused); --live narrows it. pharen apps archive retires one.
pharen authlogin signs you in from any browser and stores a session; status exits 0 when the active credential authenticates; logout deletes it.
pharen ingest-keysBeyond new: list, rotate and revoke — the lifecycle of the key your app ships with. See Keys & credentials.
pharen query-keysMint, list, rotate and revoke the read-only key you hand to a bot or an agent. See Keys & credentials.
pharen devicesThe tester provisioning queue: requests prints portal-pasteable UDID/label lines for testers whose device isn't registered yet, and resolve closes one out.
pharen configThe remote configuration your builds resolve. show prints the stored configuration and its version; shake is the remote switch for shake-to-send-feedback — it can turn the feature off in the field, and can never turn on an app that compiled it off.
pharen push-credentialsAPNs and FCM key material, read from disk and never pasted: add, bind to an app, rotate, disable, and test-send to one device.
pharen push-registrationsOne row per device registration, newest first. Structurally token-free — no push token is ever printed.
pharen diagnosticsField diagnostic bundles: what has arrived, what is still waiting, and how much room is left. pending prints, for each waiting bundle, the one sentence that unblocks it.
pharen storesConnect App Store Connect and read what Apple measures about your app from outside it — ratings and reviews, keyword and chart rank, downloads and proceeds.