Self-hosted service
PGYER/fir-cli avatar
PGYER/fir-cli

fir-cli uploads iOS and Android builds to fir.im from a terminal or a CI job

fir.im(betaqr.com) command-line interface

1,062 stars226 forksRubyMIT

At a glance

What is it?
fir-cli is a Ruby command line client for the fir.im distribution service, used to list, upload and publish iOS and Android builds, with a Docker image for CI and integrations for Jenkins, Circle CI and fastlane. Its changelog is a better guide to the service than its documentation, because it records domain changes, an expiring API and the slow drift of the underlying service.
Who is it for?
fir-cli suits a team that ships iOS or Android builds to Chinese users through the fir.im service and wants publishing in a CI job rather than in a browser, and it suits a fastlane user who wants the upload in the same lane as the build.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 165 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One service, one client, one language

fir-cli is the command line interface for fir.im, also known as betaqr.com, and its whole job is to let you view and upload iOS and Android applications without opening a browser.

That sounds like a small thing, and for a one person side project it would be. For a release engineer it is not, because uploading a build to a distribution service is a step in a pipeline, and a step in a pipeline has to be scriptable, authenticated without a human present, and able to report a result that another tool can consume. The readme is explicit that the client is for viewing and uploading, and the project is written in Ruby and published as a gem.

The audience is therefore the release engineer of a product that distributes through fir.im, which in practice means a Chinese app, and the delivery mechanism for that is a CI job. The readme documents Jenkins usage with a shell script, notes that the client can be called directly from the command line inside a fastlane lane, and points at a separate fastlane plugin for the same purpose.

There is a second implementation for people who hit installation problems or simply do not want a Ruby toolchain. A Go version exists, and it is a single binary with no dependencies, and the readme is careful to say that its features do not fully overlap with the Ruby client, with the specifics left to that project's own documentation. That is a reasonable arrangement for a service that is widely used inside one country, where a dependency-free binary is genuinely easier to run on a build agent.

The licence is MIT, with the licence file in the repository root, and the project also ships a Docker image so that the Ruby dependency problem has a second answer.

The changelog is where the real information is

The readme opens with a long changelog, and reading it tells you more about the service than the documentation does.

The release list runs from 2.0.25 back to 1.7.0, and several entries are about the service's own infrastructure rather than about the client. There is a version that changed the API domain, a version that switched the client to a backup domain because of a domain problem, and a version that moved to the backup domain again because a vendor blacklist was producing false positives. A later one explains that the CDN did not support pass-through of the PATCH method, which caused a 400 error when modifying app information, and one converted the returned download address into the bound domain.

The client side entries are equally instructive about the shape of the work. The most recent version, 2.0.25, is compatible with rubyzip 3.x and fixes an Info.plist not readable error when parsing an ipa under Ruby 3.2 and later. 2.0.24 fixed APKs containing illegal XML control characters that failed to parse. 2.0.21 fixed the publish result not being returned, which made the fastlane plugin's command result nil. 2.0.19 fixed the uploaded icon not showing, and 2.0.17 added an upload percentage display.

And then there is the one that matters most for anyone starting today. A note above the changelog asks users to upgrade to version 2.0.15 or above as soon as possible, because the old API is about to expire, and 2.0.15 is the version that changed the API domain. The standard advice for a Ruby tool with a per machine install is to run gem install fir-cli, and fir version to see what you have.

The overall picture is of a client doing genuinely useful work while tracking a service that changes underneath it. That is not a criticism of the project so much as a description of what you are depending on.

Installing it, and the Docker route for a build agent

There are three installation paths, and each suits a different situation.

The Ruby one is a gem install, and the readme is direct about doing it:

bash
gem install fir-cli

followed by a check of what you actually have:

bash
fir version

The Docker one exists because the same readme that recommends the gem install also warns that if you hit various problems installing fir-cli and are in a hurry, there is a Go version that needs no dependencies. The Docker image is the middle path, a container that carries the Ruby environment with it. The preparation is two steps: mount the files you need into the container, and pass your API token in as an environment variable. Then the run is a single command:

bash
docker run -e API_TOKEN=xxxxxxxe -v /Users/atpking/Desktop:/tmp ghcr.io/pgyer/fir-cli:latest publish  /tmp/1.apk

which reads as mounting your desktop onto the container's /tmp directory and then publishing the file that lands there, and the readme notes that other directories can be used instead. There is a practical recommendation about which registry to pull from: the GitHub hosted image is suggested as more stable to pull from inside China, with the historical Docker Hub image kept in sync, so the command only needs the image name changed.

The image itself is a two stage story in one file. It is based on ruby:3.2-slim, installs git, the certificate authorities and the build tools, installs bundler, sets a UTF-8 locale and a working directory and home, copies the project in, runs bundle install, touches a README so the gem build has something to include, runs rake install, and declares fir as the entrypoint. So the container is a Ruby environment with the gem installed in it, and the arguments after the image name are the arguments to the client.

CI, fastlane and the chat notifications

The client is built to be run by machines, and the readme shows a Jenkins script that checks the environment and then publishes:

bash
rvm list
ruby -v
gem install fir-cli
fir -v

The idea is that a Jenkins script step installs the gem if it is missing, prints the version so the log shows what ran, and then executes the publish. The script comments note that the Ruby version should be greater than 2.4.0, and that the rvm line can be commented out if Ruby was installed system wide, so it is written for a mixed estate.

The fastlane integration is the other shape. It can be called directly from the command line, or the companion fastlane plugin can be installed, and one of the changelog entries exists purely because of that integration: version 2.0.21 fixed the publish result not being returned, which made the plugin's command result nil. That is a good example of the ecosystem cost of a tool that a plugin wraps.

The notifications are the most elaborate part. The publish command can push to DingTalk, to WeCom and to Feishu, each with its own access token parameter, and the readme has a section explaining what those tokens actually are: the part of the callback URL that looks like an access token, whether that is the query parameter in a DingTalk robot URL, the key in a WeCom webhook URL, or the path segment in a Feishu bot hook URL. The feature set grew over versions, with Feishu V2 robot support, WeCom support, a twenty second callback timeout, custom messages, the ability to mention individual phone numbers or everyone, and signing authentication for DingTalk with a secret parameter.

Two of those features are worth reading for what they say. The DingTalk entry about the robot no longer supporting base64 exists because the QR code stopped displaying, and the fix came with a switch to signed authentication. And there is a parameter, skip_fir_cli_feedback, that stops the client sending statistics. For a tool you run in a corporate CI, the existence of an opt out for telemetry is worth noticing either way.

aab, bundletool, and the paths that are going away

Two of the documented limitations concern the format of what you upload, and one concerns a feature that has been removed.

Uploading an aab is possible, but only to a limited degree, and the readme sets out three conditions of which any one must be satisfied. Either the system already has bundletool available on the command line, for example through brew install bundletool, or you pass the path to a bundletool jar with the parameter for that and the command line can call java, or the download URL for the bundletool release is reachable and the automatic download parameter is used to fetch it. The reason for any of this is stated plainly: an aab file cannot be installed directly on a phone, it has to be turned into a per device apk with bundletool on a desktop.

The other limitation is on the download side rather than the upload side. There is a limit on the number of historical versions an app can keep pinned, and when the limit is reached the oldest is dropped from the list, and the readme notes that a user with a special requirement can ask for a manual adjustment. It also says, without further comment, that this limit exists for cost reasons.

The deprecated path is the older build command for ipa files. It is marked as expired, and the recommendation is to use fastlane with its gym command to build the ipa and then use fir publish to upload the result. That is a sensible division, since building a signed iOS artefact on a Linux CI agent was always the hard part.

Two smaller operational notes. There is an environment variable for the timeout, because uploads from outside China often fail with a stream closed error on a high latency connection. And an option for overseas acceleration, which is described as deprecated in favour of automatic detection: since version 2.0.18 the client detects whether the uploading machine is outside China and accelerates the upload automatically, and the manual option is marked as no longer the recommended route. A changelog entry explains that this was needed because uploads from overseas CI services such as Circle CI, Travis CI or GitHub Actions were timing out.

Support, the licence and the shape of the project

The support model is unusual enough to mention. The readme asks for feedback through a WeChat contact with a note to mention the exchange topic, or through GitHub issues, and for people who want to do continuous integration but hit a big technical obstacle it gives the same contact. There is also a section thanking an individual who provided a correction.

That is a coherent model for a service with a Chinese user base, and it is also a real dependency for an organisation outside that ecosystem: the primary support channel is a messaging app, and the GitHub issue tracker is the alternative.

The repository itself is a conventional Ruby gem project. There is a Gemfile, a Rakefile, a gemspec named fir-cli.gemspec, a bin/ directory for the executable, a lib/ directory for the code, a test/ directory, a doc/ directory with the four documentation pages for installation, login, publish and upgrade, a CHANGELOG file, and a Dockerfile. The continuous integration configuration is a .travis.yml, and there is a .codeclimate.yml and a .flow-plugin.yml for the static analysis services the project used.

On the versioning, the readme is clear that there are no GitHub releases and the version history lives in the changelog, which is a long list with the version number, the change and, for the older entries, a date in brackets. The recent versions are in the 2.0.x line, the last push was on 2026-05-11, and the repository is not archived.

The one thing the readme asks every user to do is also the thing most likely to be missed, and it is the first item in the notes section: upgrade, because the old API is expiring. A client whose service switches domains and retires API versions needs to be kept current, and gem install fir-cli is the whole procedure.

Editorial conclusion

fir-cli suits a team that ships iOS or Android builds to Chinese users through the fir.im service and wants publishing in a CI job rather than in a browser, and it suits a fastlane user who wants the upload in the same lane as the build. It is the wrong choice if you need a distributor-neutral workflow, because the client is written against one service and its API, and a poor match if you depend on its stability, since the changelog records repeated domain changes and a backup domain being used because of a vendor blacklist. Before adopting, upgrade past version 2.0.15 because the old API is being retired, check whether your build is an ipa or an aab, and read the aab conditions about bundletool before you rely on that path.

Frequently asked questions

How do I install fir-cli?

Run gem install fir-cli, which the readme recommends as the fix for most usage problems, and check the installed version with fir version. A Go rewrite with no dependencies exists for people who hit installation trouble, and there is also a Docker image that carries the Ruby environment with it.

Can fir-cli upload an Android App Bundle?

Yes, but with limited support and three conditions, of which any one must hold: bundletool is already callable on the command line, or you pass the path to a bundletool jar and can call java, or the automatic download parameter can fetch the tool. An aab cannot be installed on a phone directly, it needs bundletool on a desktop to become a per device apk.

How do I publish from Jenkins or fastlane?

The readme gives a Jenkins script that checks rvm, prints the Ruby version, installs the gem and prints the fir version before publishing. For fastlane the client can be called from the command line, or a companion fastlane plugin can be installed, and one of the recent fixes exists specifically because the publish result was not being returned to that plugin.

Why does my upload fail with a stream closed error outside China?

The readme attributes it to network latency and points at an environment variable for setting a longer timeout. Since version 2.0.18 the client also detects uploads from outside China and accelerates them automatically, so the manual overseas acceleration option is deprecated.

Official sources

  1. Issues
  2. License: MIT
  3. PGYER/fir-cli on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pgyer-fir-cli.svg)](https://hysenlabs.com/projects/pgyer-fir-cli)

Was this useful?