# karagoz install

Installs an app from one APK file, or on an iOS simulator or iPhone from a .app or .ipa, or replaces the installed version of the same app.

- Android emulator Done
- Android device Done
- iOS simulator Done
- iOS device Done

## Usage

```console title="~/app"
karagoz install <apk> [--device <id>]
```

## Before you start

- On Android: platform-tools 30.0.0 or newer.
- On an iOS simulator: Xcode, opened once.
- On an iPhone: Xcode 15 or newer, paired with the Mac, Developer Mode on (Settings > Privacy & Security > Developer Mode).

## Options

| Option          | Default                                                               | Meaning                                                                                                                                                                                                                     |
| --------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<apk>`         |                                                                       | Path to an `.apk` file, or for a simulator or an iPhone a `.app` directory or an `.ipa` file. A relative path is resolved against the current directory. One `.apk` only: split APKs, `.apks` and `.aab` are not supported. |
| `--device <id>` | see [Device selection](https://karagozmcp.com/docs/device-selection/) |                                                                                                                                                                                                                             |

## Examples

```console title="~/app"
$ karagoz install /Users/me/app/build/outputs/apk/debug/app-debug.apk
{"device":"emulator-5554","path":"/Users/me/app/build/outputs/apk/debug/app-debug.apk"}
```

## Output

```json
{
  "device": "emulator-5554",
  "path": "/Users/me/app/build/outputs/apk/debug/app-debug.apk"
}
```

The package name is not reported.

- `device` string: Serial, or the simulator's or iPhone's UDID.
- `path` string: The absolute path of the app, as given (symlinks are not followed).

## Errors

| Code                                                                        | When                                                                                                                                                                                                                                                                                                                                                                                     |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`INVALID_ARGS`](https://karagozmcp.com/docs/error-codes/#invalid-args)     | `'<path>' is not an .apk, .app or .ipa file`, `no file at '<path>'`, `'<path>' is not a file`, or for a `.app` `'<path>' is not a directory`. Checked before any device call. Once the device is picked, and before anything is sent to it: `'<path>' is an Android app; <udid> is an iOS simulator` (`is an iPhone` on one) or `'<path>' is an iOS app; <serial> is an Android device`. |
| [`INSTALL_FAILED`](https://karagozmcp.com/docs/error-codes/#install-failed) | Android refused the APK. The message is adb's, and `reason` is Android's code, as in the [example](https://karagozmcp.com/docs/output/).                                                                                                                                                                                                                                                 |
| [`ADB_FAILED`](https://karagozmcp.com/docs/error-codes/#adb-failed)         | A failure without an Android code, such as `Error: device is still booting.`                                                                                                                                                                                                                                                                                                             |
| [`ADB_TIMEOUT`](https://karagozmcp.com/docs/error-codes/#adb-timeout)       | After 30 s plus 1 s for every started MB of the APK. On a phone, the install may be held on a prompt (Play Protect, or the maker's check for USB installs) that waits for a tap; if it is accepted later, the app still installs.                                                                                                                                                        |

`INSTALL_FAILED` codes seen on the emulator:

| `reason`                                | Cause                                         |
| --------------------------------------- | --------------------------------------------- |
| `INSTALL_FAILED_VERSION_DOWNGRADE`      | A lower `versionCode` than the installed app. |
| `INSTALL_FAILED_UPDATE_INCOMPATIBLE`    | Signed with another key.                      |
| `INSTALL_PARSE_FAILED_NOT_APK`          |                                               |
| `INSTALL_FAILED_DEPRECATED_SDK_VERSION` | `targetSdkVersion` below 24 on Android 16.    |

On a phone, Android’s code can come from the phone’s own install check, with text that says someone cancelled when nobody did:

| `reason`                              | Message                     | Cause                                                                 |
| ------------------------------------- | --------------------------- | --------------------------------------------------------------------- |
| `INSTALL_FAILED_VERIFICATION_FAILURE` |                             | Play Protect refused the app.                                         |
| `INSTALL_FAILED_USER_RESTRICTED`      | `Install canceled by user`  | Xiaomi, Redmi, POCO: “Install via USB” is off, or its prompt ran out. |
| `INSTALL_FAILED_ABORTED`              | `User rejected permissions` | vivo, iQOO: the same.                                                 |

Every install can also get the device selection errors and the adb errors.

## Notes

- Runs `adb install -r --no-incremental`. Platform-tools older than 30.0.0 forward `--no-incremental` to the device, and the device rejects it.
- There is no way to pass `-g` (grant runtime permissions), `-d` (allow a downgrade) or `-t`. An APK marked `testOnly`, which Android Studio’s Run button can produce, fails with `INSTALL_FAILED_TEST_ONLY`.
- Android installs the app for every user on the device that allows adb installs; Secure Folder, Dual Messenger and work profiles get it too.
- On Android 17, installing over the app in front puts a system placeholder in its task. The app restarts 2 to 20 s after `install` returns. A `launch` in that window exits 0, reports `com.android.systemui/com.android.wm.shell.packageupdate.PackageUpdateActivity` as `activity` and starts nothing.

## On an iOS simulator

Takes a `.app` bundle directory or an `.ipa`, built for the simulator, and runs `simctl install`.

```json
{
  "device": "2EC769C1-2EF2-47EB-A441-E6843968F039",
  "path": "/Users/me/Build/Products/Debug-iphonesimulator/Probe.app"
}
```

- A build for a physical iPhone is refused: `INSTALL_FAILED` with `reason` `IXUserPresentableErrorDomain:4` and simctl’s message, which says the executable has no code for the simulator. Other refusals carry simctl’s own `<domain>:<code>`, such as `IXErrorDomain:13` for a bundle without `Info.plist`. A failure without one stays [`SIMCTL_FAILED`](https://karagozmcp.com/docs/error-codes/#simctl-failed).
- A lower `CFBundleVersion` than the installed app is not refused. The app’s data is kept across reinstalls.
- [`SIMCTL_TIMEOUT`](https://karagozmcp.com/docs/error-codes/#simctl-timeout) after 30 s plus 1 s for every started MB of the file; a `.app` gets 31 s.
- Over an app stopped in a debugger (an Xcode breakpoint or pause), the install waits for the debugger. The call ends in `SIMCTL_TIMEOUT`, and the app is installed once it continues or the debug session ends. Meanwhile other calls to that simulator can time out too.

## On an iPhone

Takes a `.app` bundle directory or an `.ipa` built and signed for this iPhone, and runs `devicectl device install app`. Karagöz does not re-sign the app: build it for the phone in Xcode or Flutter, which sign it with a profile that includes this iPhone.

- The phone checks the signature first. A refusal is `INSTALL_FAILED`, whose `reason` is the phone’s own `<domain>:<code>`, the deepest error devicectl reports. The message names the cause, then says that Karagöz does not re-sign:

  - a simulator build: `MIInstallerErrorDomain:13` with `0xe8008014 (The executable contains an invalid signature.)`
  - an unsigned app: `0xe800801c (No code signature found.)`
  - a profile that does not include this iPhone: `0xe8008015` or `0xe8008012`, from public reports (not measured here)

- A bundle without `Info.plist` is refused on the Mac: `com.apple.dt.CoreDeviceError:3000`.

- A reinstall replaces the version, a lower one too, and keeps the app’s data. Installing over the running app stops it. A locked iPhone installs too.

- [`DEVICECTL_TIMEOUT`](https://karagozmcp.com/docs/error-codes/#devicectl-timeout) after 30 s plus 1 s for every started MB of the file; a `.app` gets 31 s.

## MCP equivalent

MCP equivalent `install {"apk":"/Users/me/app/build/outputs/apk/debug/app-debug.apk"}` [MCP tools](https://karagozmcp.com/docs/mcp/#tools)

Arguments: `apk` (required), `device`. The names are the CLI's option and positional names.
