Skip to content

AscEmon/FlutterShip

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🚀 FlutterShip

An AI agent that builds, signs, and ships your Flutter app — APK, App Bundle, and IPA — from one terminal. Talk to it in plain English.

fluttership "build apk aab ipa for DEV and LIVE"

One command. Parallel builds. No forgotten version bumps, no wrong keystore passwords, no babysitting.

If FlutterShip saves you time, please ⭐ star the repo — it genuinely helps the project reach more Flutter devs and keeps it moving.


Table of contents


What is FlutterShip?

FlutterShip is a Python CLI with an AI agent at its core. You point it at your Flutter project and tell it what you want — "build a LIVE apk", "release 2.0.0 to TestFlight" — and it does the rest:

  • Reads your project automatically — version, flavors, dart-define keys, bundle id, signing config. No config file to write.
  • Detects your build pattern — flavors, --dart-define, --dart-define-from-file, or plain. It figures out the right flutter build command for you.
  • Builds in parallel — APK + AAB + IPA across multiple flavors at once, each in an isolated sandbox.
  • Signs and submits — uploads AAB to Google Play and IPA to TestFlight/App Store, with credentials kept in an encrypted vault the AI never sees.
  • Explains failures in plain English — when a build breaks, it reads the log and tells you exactly what to fix.
  • Keeps a release history — every build and upload is logged outside your repo.

You can drive it conversationally (the agent decides which tools to run) or with plain subcommands (fluttership doctor, fluttership status, …).


Quick start

FlutterShip needs an LLM API key to power the agent. The easiest free option is Google Gemini — the free tier is plenty for normal use.

1. Get a free Gemini API key

  1. Go to aistudio.google.com and sign in.
  2. Click Get API keyCreate API key.
  3. Copy the key (looks like AIza...).

Want another free option? FlutterShip also supports groq — free and very fast (Llama / open models). Get a key at console.groq.com/keys. Prefer Claude or GPT? anthropic (console.anthropic.com) and openai (platform.openai.com) work too. Pick your provider during setup, or switch anytime in-session with /model.

2. Install FlutterShip

Recommended — with pipx (installs the CLI in its own isolated environment and puts it on your PATH automatically — the cleanest way to install any Python command-line tool):

pipx install fluttership
Don't have pipx? (one-time setup)
python3 -m pip install --user pipx
python3 -m pipx ensurepath        # adds pipx's bin dir to PATH
# then open a new terminal and run:  pipx install fluttership

Or with pip:

python3 -m pip install fluttership

💡 Use python3 -m pip rather than a bare pip/pip3. It works the same on every machine regardless of whether the command is called pip or pip3, and installs into the Python you're actually running.

The install is intentionally lean and fast — it pulls in only what the default (free Gemini) provider needs. Other providers and Play Store uploads are optional add-ons you install only if you use them:

python3 -m pip install "fluttership[anthropic]"   # use Claude as the provider
python3 -m pip install "fluttership[openai]"      # use GPT as the provider
python3 -m pip install "fluttership[play]"        # upload AAB to Google Play
python3 -m pip install "fluttership[all]"         # everything

fluttership: command not found?

If your shell can't find the command right after a pip install, the Python scripts directory isn't on your PATH (common with pip install --user). Two fixes:

  • Easiest: install with pipx instead (see above) — it handles PATH for you.

  • Always works: run it through Python, which never depends on PATH:

    python3 -m fluttership            # identical to running `fluttership`
    python3 -m fluttership doctor

    To add the scripts dir to your PATH permanently, append the folder printed by python3 -m site --user-base (+ /bin, or \Scripts on Windows) to your shell profile (~/.zshrc, ~/.bashrc, …).

Install from source (for development)
git clone https://github.com/AscEmon/fluttership.git
cd fluttership

python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

python3 -m pip install -e .          # core only
python3 -m pip install -e ".[all]"   # with every provider + Play upload

3. First run — the setup wizard

From inside any Flutter project, run:

cd /path/to/your/flutter/app
fluttership

On first run a ~30-second wizard walks you through:

  • Mode — solo (just you) or team (a lead shares credentials).
  • AI provider + model — pick gemini and the default model.
  • Your API key — paste it and FlutterShip saves it to ~/.fluttership/.env (permissions 600), or set it yourself (see below).

After that, you're talking to the agent. That's it.

Setting the API key manually (optional)

Instead of pasting it into the wizard, you can put it in a .env file in your project root, or in ~/.fluttership/.env:

# Pick one, matching your chosen provider
GEMINI_API_KEY=your_gemini_api_key
# GROQ_API_KEY=your_groq_api_key
# ANTHROPIC_API_KEY=your_anthropic_api_key
# OPENAI_API_KEY=your_openai_api_key

# Optional — override provider/model without re-running the wizard
# (or just run /model in-session)
# LLM_PROVIDER=gemini
# GEMINI_MODEL=gemini-2.5-flash

Project .env wins; ~/.fluttership/.env fills the gaps. Both are git-ignored.

Verify everything is wired up:

fluttership doctor

How you actually use it

There are two ways to drive FlutterShip — mix them however you like.

A) Talk to the agent (natural language)

Just run fluttership and type what you want. Or pass it inline:

fluttership "build LIVE apk"
fluttership "build apk aab ipa for DEV and LIVE"     # parallel matrix
fluttership "release version 2.0.0 to production"
fluttership "what was the last release?"

The agent reads your project, shows a short build plan, asks for confirmation, builds, and offers the right next step (e.g. "Submit to TestFlight?" only after an IPA is built).

Inside an interactive session, type / to open the command palette (/status, /doctor, /vault, /mode, /new, /exit, …). Press Esc to interrupt the agent mid-thought and redirect it.

B) Plain subcommands (no LLM involved)

fluttership status      # version, bundle id, detected build pattern
fluttership doctor      # health checks — tools, signing, vault, API key
fluttership history     # build & release history for this project
fluttership clean       # delete build artifacts (fluttership-dist/)
fluttership uninstall   # remove FlutterShip + all its data from your machine

Command reference

Command What it does
fluttership Start the interactive AI agent
fluttership "<request>" Run a one-off request, e.g. "build LIVE apk"
fluttership setup Re-run the setup wizard (mode / provider / model)
fluttership mode Show or switch solo/team mode
fluttership mode solo Switch to solo mode
fluttership mode team lead|member Switch to team mode with a role
fluttership config Show current configuration
fluttership doctor Environment + project health checks
fluttership status Project info — version, bundle id, build pattern
fluttership history Build & release history
fluttership clean Delete build artifacts (fluttership-dist/)
fluttership vault setup Create the encrypted credentials vault (team: lead only)
fluttership vault verify Test the vault password + show what's configured
fluttership vault status Vault info (no password needed)
fluttership vault log Credential-access audit log
fluttership vault export [dest] Share the encrypted vault with your team (lead only)
fluttership vault import <file> Install a vault shared by your team lead
fluttership uninstall Remove FlutterShip completely. --keep-data keeps vault/history, --yes skips the prompt
fluttership help Full usage

Slash commands (inside an interactive session): /status, /history, /doctor, /clean, /mode, /model, /config, /vault, /setup, /new, /exit.


⚡ Parallel builds

FlutterShip's headline feature. Ask for several targets and/or flavors at once and it builds them concurrently, each job in its own isolated git worktree sandbox:

fluttership "build apk aab ipa for DEV and LIVE"

A live dashboard shows every job's status as it runs:

⚡ Building 6 jobs · v2.0.0+9
┌──────────────┬──────────────┬────────┬──────────────────────────────┐
│ Job          │ Status       │   Time │ Detail                       │
├──────────────┼──────────────┼────────┼──────────────────────────────┤
│ apk·DEV      │ ✅ done      │  1m42s │ myapp_DEV_2.0.0(9).apk       │
│ apk·LIVE     │ ✅ done      │  1m48s │ myapp_LIVE_2.0.0(9).apk      │
│ aab·LIVE     │ 🔨 building  │  0m55s │ logs/aab-LIVE.log            │
│ ipa·LIVE     │ 🍎 archiving │  2m10s │ logs/ipa-LIVE.log            │
└──────────────┴──────────────┴────────┴──────────────────────────────┘

Why it's worth it:

  • Faster releases — wall-clock time is your slowest build, not the sum of all of them.
  • No collisions — each job runs in a fresh, isolated worktree, so Gradle's project lock and the shared build/ directory never clash. iOS archives are automatically serialized (Xcode's global state can't take two Runner archives at once), while Android builds stay fully parallel.
  • One shared version — every artifact in a run carries the same version+build number, so a matrix release is consistent.
  • Artifacts collected safely — finished builds land in fluttership-dist/ (outside build/), so flutter clean can't wipe what you just built. Per-job logs go to build/fluttership-logs/.
  • Different flavors per platform"LIVE and DEV apk, plus LIVE ipa only" builds exactly those pairs, no wasted builds.

Run fluttership clean to reclaim space from fluttership-dist/ when you're done (your release history, kept in ~/.fluttership, is never touched).


🔐 The secure vault

Store credentials (App Store .p8 key, Play Store service-account JSON, IDs) are sensitive. FlutterShip keeps them in an encrypted vault — never in your repo, never in plain text, and never shown to the AI model.

How it's protected:

  • AES-256-GCM authenticated encryption with a memory-hard key derivation. The key is derived from your password and never stored — the file holds only salt, nonce, and ciphertext.
  • Lives in ~/.fluttership/vault.enc (chmod 600) — outside your project.
  • Strong password required: the file is safe to share, so its only defense is the password.
  • Decrypted in memory only; temp files written to /tmp (chmod 600) are deleted immediately after each upload.
  • The LLM only ever sees success/fail status — never a key, password, or token.
  • Every access is written to an audit log (fluttership vault log).

Set it up once:

fluttership vault setup

It asks for your Play Store service-account JSON, App Store .p8 key, and the matching IDs (the next section shows exactly how to get each one), then asks you to set a vault password.

fluttership vault verify    # test your password + see what's configured
fluttership vault status    # info only — no password needed

Choose a strong vault password and store it in a password manager. If you share the vault with a team, never send the password over chat or email — use a shared password-manager item.


📦 Publishing to the stores

To upload builds, FlutterShip needs store credentials in the vault. This section is a complete, click-by-click guide to finding every value. Run fluttership vault setup once you have them ready.


🍎 App Store / TestFlight (iOS — IPA)

You need an App Store Connect API key, which gives you four things: a .p8 key file, a Key ID, an Issuer ID, and your app's Bundle ID.

Who can do this? You need the Admin or Account Holder role in App Store Connect to create an API key. If you don't have it, ask whoever owns your Apple Developer account to follow these steps and hand you the four values.

Step 1 — Open the Keys page

  1. Go to appstoreconnect.apple.com and sign in.
  2. Click Users and Access (top menu).
  3. Open the Integrations tab, then select App Store Connect API in the sidebar (on some accounts this is shown directly as a Keys tab).

Step 2 — Copy the Issuer ID

At the top of the Keys page you'll see Issuer ID — a long UUID like 69a6de70-1234-47e3-e053-5b8c7c11a4d1.

➡️ This is your Issuer ID. Copy it.

Step 3 — Generate the key and copy the Key ID

  1. Click the (+) button to generate a new key.
  2. Give it a name (e.g. FlutterShip).
  3. Set Access to App Manager (enough to upload builds).
  4. Click Generate.
  5. The new key appears in the list with a 10-character Key ID like 6A3W84GZKK.

➡️ This is your Key ID. Copy it.

Step 4 — Download the .p8 file (⚠️ one time only)

Next to your new key, click Download API Key. You'll get a file named AuthKey_XXXXXXXXXX.p8.

⚠️ Apple lets you download this file only once. Save it somewhere safe (a password manager or secure folder). If you lose it, you must revoke the key and create a new one. FlutterShip will copy it into the encrypted vault, so you don't need to keep it in your project — and you should never commit it to git.

➡️ This is your .p8 key file.

Step 5 — Find your Bundle ID

Your Bundle ID is your app's identifier, e.g. com.yourcompany.yourapp. It must match:

  • Xcode → Runner target → Signing & CapabilitiesBundle Identifier, and
  • the app record in App Store Connect → My Apps.

Run fluttership status and FlutterShip prints the bundle id it detected — use that.

➡️ This is your Bundle ID.

Step 6 — The app must already exist

Before your first upload, the app has to exist in App Store Connect: My Apps → (+) → New App, using the same Bundle ID. Uploads go to TestFlight via xcrun altool, so iOS publishing requires macOS + Xcode.

Recap — what to paste into fluttership vault setup

Value Example Where it came from
.p8 key file path ~/Downloads/AuthKey_6A3W84GZKK.p8 Step 4
Key ID 6A3W84GZKK Step 3
Issuer ID 69a6de70-1234-47e3-… Step 2
Bundle ID com.yourcompany.app Step 5

🤖 Google Play (Android — AAB)

You need a service-account JSON with Google Play Developer API access. This is a two-place setup: you create the account in Google Cloud Console and grant it permission in the Play Console.

Who can do this? Creating and linking a service account needs the Play Console account owner (or an admin the owner has allowed to manage API access). If that's not you — for example your company's Android lead owns the account — ask them to do Steps 1–4 and send you the downloaded JSON file. They never have to touch FlutterShip; you just add the JSON to your vault.

Step 1 — Open Play Console API access

  1. Go to play.google.com/console and sign in.
  2. In the left sidebar, open Setup → API access.
  3. If prompted, accept the terms and link a Google Cloud project (Play will create one for you, or you can pick an existing one).

Step 2 — Create the service account

  1. On the API access page, find Service accounts and click Create new service account.
  2. Follow the link to Google Cloud Console that Play opens for you.
  3. In Google Cloud Console: Create Service Account → give it a name (e.g. fluttership-uploader) → Done. You don't need to grant Cloud roles here.

Step 3 — Download the JSON key

  1. Still in Google Cloud Console, open your new service account.
  2. Go to the Keys tab → Add key → Create new key.
  3. Choose JSON and click Create. A file like myapp-uploader-1a2b3c4d5e6f.json downloads automatically.

➡️ This is your service-account JSON. Keep it secret — FlutterShip stores it in the encrypted vault, and it must never be committed to git.

Step 4 — Grant it permission in Play Console (the step everyone forgets)

Back in Play Console → Setup → API access, your new service account now appears in the list. Click Manage Play Console permissions (or Grant access) next to it and give it:

  • Releases → Release apps to testing tracks ✅ (needed for internal/alpha/beta)
  • Releases → Release to production ✅ (only if you'll push to production)
  • Release → Manage store presence ✅ (recommended)

Click Invite user / Apply and wait a few minutes for permissions to propagate.

Granting access only in Google Cloud is not enough — the service account must be granted access inside Play Console too. Missing this is the #1 cause of the 403 The caller does not have permission error (see Troubleshooting).

Step 5 — Enable the Play Developer API

In console.cloud.google.comAPIs & Services → Library, search for Google Play Android Developer API and click Enable (for the same Cloud project Play linked in Step 1).

Step 6 — The app must already exist (with one manual upload)

The app has to exist in Play Console with your package name, and Google requires at least one manual upload through the browser before the API will accept uploads for a brand-new app. After that first manual AAB/APK, FlutterShip can take over.

Recap — what to paste into fluttership vault setup

Value Example Where it came from
Service account JSON path ~/Downloads/myapp-uploader-1a2b3c.json Step 3
Package name com.yourcompany.app your app's applicationId

Play uploads default to the internal track (safest). Change it with fluttership config or by telling the agent "release to production". The Play Store rejects duplicate version codes, so FlutterShip prompts you to confirm the version + build number for every store-bound build.


Then just ask

fluttership "release 2.0.0 to TestFlight and Play Store"

FlutterShip unlocks the vault (you enter the vault password), uploads, and records it as a release in your history. A build that was only compiled — never uploaded — stays a "build", not a "release".


Solo vs Team mode

Solo — everything lives on your machine. You own the vault and the releases.

Team — a shared-vault workflow:

  • The lead runs vault setup once, then vault export to produce the encrypted fluttership-vault.enc file and hands it to members.
  • Members run vault import <file> and can build + submit, but never manage or re-export credentials.
  • The vault file is safe to share (it's AES-256-GCM encrypted and useless without the password). Share the password only through a team password manager.
fluttership mode team lead      # you manage credentials
fluttership mode team member    # you import the lead's vault

Requirements

  • Python 3.9+
  • Flutter SDK (on your PATH)
  • git and rsync (rsync powers the isolated parallel-build sandboxes)
  • For iOS builds/uploads: macOS + Xcode + CocoaPods
  • An LLM API key (Gemini free tier works great)

Run fluttership doctor any time to check all of the above.


Security model

  • Credentials are never committed — the vault lives in ~/.fluttership/, outside your repo, and .p8/.jks/.json/.env files are git-ignored by default.
  • The AI never sees secrets — it receives only success/fail status and file paths, never key material.
  • Encryption at rest — AES-256-GCM with a memory-hard key derivation; the key is derived from your password and never stored. Decrypted only in memory; temp files wiped after use.
  • Strong password required — the encrypted file's security reduces to password strength.
  • Audit trail — vault unlocks, exports, imports, and uploads are logged.
  • Built artifacts can embed compiled code and dart-define values, so fluttership-dist/ is git-ignored automatically — never commit your builds.

If you fork or publish this repo, scrub any API keys from your git history before making it public — a key committed even once stays in the history until rewritten.


🧯 Troubleshooting

Most problems fall into one of these. If yours isn't here, please open an issue — and if you solve something new, a PR that adds it to this list is hugely appreciated.

Play Store: 403 The caller does not have permission

The service account can authenticate but isn't allowed to touch this app. Check, in order:

  1. Permission granted inside Play Console, not just Google Cloud — Play Console → Setup → API access → your service account → grant Release apps to testing tracks (and Release to production if needed). Wait ~5–10 minutes after granting.
  2. The Play Developer API is enabled for the linked Cloud project (Cloud Console → APIs & Services → Library → Google Play Android Developer API → Enable).
  3. Package name matches exactly — compare fluttership vault verify with fluttership status and with the app in Play Console.
  4. The app exists and had one manual upload — a brand-new app needs its first AAB/APK uploaded through the browser before the API will accept uploads.
App Store: Failed to load AuthKey file (-43)

xcrun altool looks for the key in specific folders. FlutterShip handles this by copying your vault's .p8 to ~/.appstoreconnect/private_keys/AuthKey_<KeyID>.p8 for the upload and deleting it right after. If you hit this error:

  • Make sure the Key ID in the vault matches the .p8 file you uploaded during setup (re-run fluttership vault setup if unsure).
  • Confirm your Issuer ID is correct (top of the App Store Connect Keys page).
App Store: bundle id does not match / no suitable application records

The app must already exist in App Store Connect with the same Bundle ID as your Xcode project. Create it under My Apps → (+) New App, then retry.

Gemini: 429 RESOURCE_EXHAUSTED or limit: 0

Your Gemini free-tier quota is exhausted or unavailable for that model/region. Options:

  • Switch model in-session with /model, or set GEMINI_MODEL in ~/.fluttership/.env.
  • Use a different provider (groq is free and fast): set LLM_PROVIDER=groq and GROQ_API_KEY=....
Gemini: 403 API key ... reported as leaked

Google auto-disables any key that was ever pushed to a public repo. Create a new key at aistudio.google.com, update your .env, and make sure .env is git-ignored. If it was committed, rewrite history to remove it.

--flavor build fails: "does not define any product flavors"

Your project doesn't use flavors — it may use --dart-define, --dart-define-from-file, or plain builds. Run fluttership status to see the detected pattern. FlutterShip picks the right command automatically; if it guessed wrong, tell the agent the values explicitly (e.g. "build LIVE apk with flavorType=LIVE").

fluttership: command not found

The Python scripts dir isn't on your PATH. Install with pipx, or run python3 -m fluttership. See Quick start for the permanent PATH fix.


🤝 Contributing

FlutterShip is open source and built for the whole Flutter community — contributions of every size are welcome, from fixing a typo to adding a new provider. If you're reading this far, you're exactly the kind of person the project needs. 💙

A quick ⭐ on the repo is the single most helpful thing you can do — it's how other Flutter devs discover the project.

Good first contributions

  • Add a fix you discovered to the Troubleshooting section.
  • Improve error messages or the plain-English build-failure explanations.
  • Test on a build setup we don't cover yet (different flavor/dart-define layouts) and report what happened.
  • Improve docs — clearer wording, screenshots, translations.

Development setup

# 1. Fork the repo on GitHub, then clone your fork
git clone https://github.com/<your-username>/fluttership.git
cd fluttership

# 2. Create an isolated environment
python3 -m venv .venv
source .venv/bin/activate           # Windows: .venv\Scripts\activate

# 3. Install in editable mode with all extras
python3 -m pip install -e ".[all]"

# 4. Sanity-check your setup
fluttership doctor

Because it's installed with -e (editable), your code changes take effect immediately — no reinstall needed.

Project layout

fluttership/
├── agent/
│   ├── agent.py              # the AI agent — orchestrates tools via the LLM
│   ├── llm_provider.py       # provider abstraction (gemini/groq/anthropic/openai)
│   └── tools/
│       ├── runner.py         # safe subprocess + live-streaming build output
│       ├── reader.py         # reads pubspec / gradle / key.properties / xcodeproj
│       ├── validator.py      # pre-flight checks
│       ├── builder.py        # build-pattern detection + build engine
│       ├── ios_checker.py    # iOS pre-flight (Xcode/cert/pods)
│       ├── vault.py          # encrypted credential vault
│       └── submitter.py      # App Store + Play Store submission
├── cli/
│   └── main.py               # CLI entry point + subcommands
└── setup.py

New to the codebase? A great way in is to read agent/agent.py (how the agent decides what to do) and then the tool it calls in agent/tools/.

Submitting a change

  1. Create a branch: git checkout -b fix/short-description
  2. Make your change. Keep it focused — one logical change per PR.
  3. Match the existing style: clear names, docstrings on functions, small focused functions.
  4. Test it against a real Flutter project (fluttership doctor, then a build).
  5. Commit with a clear message: git commit -m "Fix: <what and why>"
  6. Push and open a Pull Request against main, describing what you changed and why, plus how you tested it. Screenshots/terminal output are very welcome.

Reporting bugs & requesting features

Open an issue with:

  • What you ran (the exact command) and what you expected.
  • What actually happened — paste the terminal output (redact any keys/passwords).
  • Your environment: OS, python3 --version, flutter --version, FlutterShip version.

Ground rules

  • Never commit secrets — API keys, .p8/.jks/.json files, passwords. Double-check your diff before pushing.
  • Be kind and constructive. We're all here to make Flutter releases less painful.

Uninstalling

To remove FlutterShip completely from your machine:

fluttership uninstall

This deletes ~/.fluttership/ — your config, the encrypted vault (signing credentials, store API keys), and your release history — and then uninstalls the pip package. It asks you to type uninstall to confirm, because wiping the vault is irreversible.

fluttership uninstall --keep-data   # uninstall the tool, keep your vault + history
fluttership uninstall --yes         # skip the confirmation prompt

Want to remove only build artifacts from a project (not the whole tool)? Use fluttership clean instead.


License

MIT


Built by Abu Sayed Chowdhury — a Flutter dev who got tired of spending 3 hours on a 10-minute release.
If FlutterShip helps you, please ⭐ star the repo and share it with your team.

About

AI agent that automates Flutter app releases — APK, App Bundle & IPA — with one command.

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages