# @drxiaozhi/minapk 中文文档

规范地址：https://npmcn.com/package/%40drxiaozhi/minapk/

版本：0\.5\.1
npm 元数据更新时间：2026-10-26T13:00:37.116Z
中文译文更新时间：2026-11-12T22:07:20.983Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40drxiaozhi/minapk
原文对照：https://npmcn.com/package/%40drxiaozhi/minapk/?lang=original
项目主页：https://github.com/jjtseng93/minapk#readme
源码仓库：https://github.com/jjtseng93/minapk

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：52.3 MB，48 个文件
- 模块格式：ES Module
- TypeScript 类型：未声明
- 运行依赖：0 个
- 首次发布：2026-08-18T05:18:57.410Z
- 最新版本发布：2026-10-26T13:00:36.874Z
- 近 30 天下载量：4188（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：8（近 12 个月 8 个）

## 中文简介

从 Bun 单文件可执行文件或源代码构建 Android APK

## 安装

```sh
npm install @drxiaozhi/minapk
```

也可以使用：`pnpm add @drxiaozhi/minapk`、`yarn add @drxiaozhi/minapk`、`bun add @drxiaozhi/minapk`

## README

# minapk

-   [English Readme](https://npmcn.com/package/%40drxiaozhi/minapk/README.en.md)
    
-   把你的Bun單一可執行檔 包成APK
    
-   minapk 是一個不使用 Gradle 的 Android APK 建置專案，以命令列工具完成資源處理、Java 編譯、R8/DEX、APK 封裝、zipalign 與簽章，並將 [Buninu 幫你牛使用者空間](https://www.npmjs.com/package/buninu) 執行環境放入 APK。
    
-   `npx @drxiaozhi/minapk /path/to/your.elf` 把 elf 路徑當位置參數傳入即可（詳細說明在後面）
    
-   原始碼：[github.com/jjtseng93/minapk](https://github.com/jjtseng93/minapk)
    
-   Buninu 幫你牛使用者空間原始碼：[github.com/jjtseng93/buninu](https://github.com/jjtseng93/buninu)
    
-   本專案衍生自 [Promastergame/tinyapk-lab](https://github.com/Promastergame/tinyapk-lab)。
    

* * *

-   [目錄(完整章節導覽)](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E7%9B%AE%E9%8C%84)

> 以下內容從建置 APK 開始。如果你已經裝好 APK，只想知道裡面怎麼操作， 請直接前往 [User Manual: Inside the APK](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-user-manual-inside-the-apk)。

## 安裝依賴

### Termux

```sh
pkg update
pkg install aapt aapt2 zip unzip openjdk-21 nodejs npm
npm install -g bun
bun upgrade
```

### Debian/Ubuntu arm64 inside Termux proot

```sh
apt update
apt install aapt zipalign zip unzip openjdk-21-jdk-headless nodejs npm
npm i -g @oven/bun-linux-aarch64-android --force
export PATH=$(npm root -g)/@oven/bun-linux-aarch64-android/bin:$PATH
bun upgrade

# when running the build you need to see something like this: libbun.so not found; copying from: /usr/local/lib/node_modules/@oven/bun-linux-aarch64-android/bin/bun
```

## 兩分鐘快速開始-你好世界專案

以下步驟假設上一節的依賴都已安裝完成。先在 Termux 建立一個只有 `console.log` 的 Bun 程式：

```sh
mkdir -p ~/minapk-hello
cd ~/minapk-hello
printf 'console.log("# Hello World!")\n' > hlw.js
```

把它編譯成 Android Bun 單一可執行檔。這組參數的順序可以用 「寶寶非常明白」（`bbfcmb`）記憶：`bun build` 是 `bb`，後面依序是 `format`、`compile`、`minify`、`bytecode`（`fcmb`）。

```sh
bun build --format=esm --compile --minify --bytecode ./hlw.js
```

目前目錄會出現 `hlw`，接著用 minapk 把它包進 APK：

```sh
npx @drxiaozhi/minapk ./hlw -n HelloWorld -p com.minapk.hello
```

第一次執行時可能會出現最多三次確認提示，依序全部輸入 `y`：安裝 minapk、允許匯出 Buninu payload，以及安裝 Buninu。完成後目前目錄會得到 `hlw.apk`。

第一次讓 Termux 存取共享儲存空間時，執行下面第一行並在 Android 權限視窗 選擇允許；已授權過就不必再執行。然後把 APK 複製到下載資料夾：

```sh
termux-setup-storage
cp hlw.apk /sdcard/Download
```

部分裝置執行 `termux-setup-storage` 後仍不能寫入 `/sdcard/Download`，需要到 Android「設定」→「應用程式」→「特殊應用程式權限」→「管理所有檔案」中， 手動允許 Termux 管理所有檔案；不同廠牌的選單名稱可能略有不同。

最後在 Android 的「檔案」或其他檔案管理器中：

1.  打開「下載」並點選 `hlw.apk`。
2.  第一次從這個檔案管理器安裝時，依提示允許「安裝未知應用程式」。
3.  若 Google Play Protect 顯示安全驗證警告，展開「更多詳細資料」並選擇 「仍要安裝」。只應對你自己建置、且確認來源的 APK 這樣做。
4.  安裝完成後點「開啟」。終端機畫面會執行 `hlw` 並顯示 `# Hello World!`。

## 應用程式名稱與輸出檔案名稱

```sh
npx @drxiaozhi/minapk -n MyApp
```

`-n`/`--appname <name>` 決定：

-   Android 顯示的應用程式名稱。
-   建置輸出的 APK 名稱，例如 `MyApp.apk`。
-   repack 輸入與輸出名稱，例如 `MyApp.apk` → `MyAppr.apk`。

不帶 `-n` 時，會用專案根目錄 `appname.txt` 目前的值（`Hello2`）當預設；minapk 不會寫入或修改這個檔案，所以不帶 `-n` 的建置永遠得到同一個、可預期的名稱。

## 安卓套件名稱

```sh
npx @drxiaozhi/minapk -p com.drjohn.bunwv
```

`-p`/`--pkgname <pkgname>` 決定 Android 的 package name/application ID，不是 APK 檔名。它應使用反向網域格式，並且必須是有效的 Java package 名稱；`repack.sh` 不會改變 package name，只有完整建置會套用。

建議使用 `com.<6 字元>.<5 字元>` 的格式，讓完整 package name 維持 16 個 ASCII 字元，例如 `com.drjohn.bunwv`。這不是 Android 的強制限制； 採用此長度是因為字串 `com.termux/files` 同樣是 16 bytes，未來可能可以 對含有該固定路徑的 Termux binary 進行等長 patch，而不必移動 binary 內的其他資料。目前的 build 流程尚未自動進行這種 patch。

不帶 `-p` 時，會用專案根目錄 `pkgname.txt` 目前的值（`com.drjohn.bunwv`）當預設，同樣不會被 minapk 寫入或修改。

## 建置步驟

```sh
npx @drxiaozhi/minapk [/path/to/your.elf]
```

> \[!NOTE\] 用 `npx` 執行（不是本地 checkout）時，第一次建置實測會依序遇到最多 3 次確認提示，全部輸入 `y`（或 `Y`）即可，這是正常流程：
> 
> 1.  `npx` 詢問是否安裝 `@drxiaozhi/minapk` 本身。
> 2.  找不到本地 `no_backup`（npm 上發佈的套件本來就不含它）時，`build.sh` 詢問是否執行 `npx buninu@latest --export`。
> 3.  上一步的 `npx` 詢問是否安裝 `buninu`。
> 
> 因為每次 `npx` 都是全新的暫存環境，第 2、3 步幾乎每次執行都會再問一次。

這是主要入口，整個流程都由 `index.js` 驅動：

1.  若有帶 elf 路徑，這次建置就用它當 `libmain.so` 打包（不會寫入專案根目錄，只影響這一次；不帶 elf 時使用專案根目錄現有的 `libmain.so`，見下方說明）。
2.  準備 Buninu payload 與 manifest/資源/Java 原始碼，用 aapt2、ECJ、R8 編譯，封裝 Buninu payload 與原生函式庫，執行 zipalign，並用 `tools/debug.keystore` 簽章（不存在時自動建立），輸出到專案根目錄的 `<appname>.apk`（例如目前設定會是 `Hello2.apk`）。
3.  build 成功後，把輸出的 APK 複製一份到**你目前的工作目錄（cwd）**：有帶 elf 時檔名是 elf 本身的檔名（去掉副檔名，若原本就沒有副檔名也一樣正確處理）加上 `.apk`，例如 `myapp.elf` 會產生 `myapp.apk`；沒帶 elf 時就是 `<appname>.apk`——例如照目前設定直接執行 `npx @drxiaozhi/minapk`（不帶任何參數），會在你執行指令當下的目錄產生 `Hello2.apk`。這一步是為了透過真正的 `npx`（套件裝在 npx 暫存快取，跟你的 cwd 是兩回事）執行時也拿得到成品；如果你的 cwd 剛好就是專案根目錄，這一步會自動跳過（APK 本來就已經在那裡了）。

在有 checkout 的情況下也可以直接跑（效果相同）：

```sh
bun ./index.js
```

實際編譯步驟由 `index.js` 內部 spawn 的 `build.sh` 完成，它是 POSIX shell script，執行環境需要有 `/bin/sh`（或 Android 的 `/system/bin/sh`）；一般不需要直接呼叫它。

-   `libmain.so`: 必須是使用 Android Bun 編譯、以 /system/bin/linker64 為執行載入器的執行檔（Bun >= 1.4 支援）。Android App 啟動時會自動執行。如果專案根目錄包含此檔案，建置時會將它封裝到 APK 的原生 lib/arm64-v8a 目錄中；若不存在則會直接略過，不會報錯。
    
-   封裝原生函式庫前，若專案根目錄沒有 `libbun.so`，會執行 `which bun`，並將找到的 Bun 複製為根目錄的 `libbun.so`；找不到 Bun 或複製失敗時會停止建置。該 Bun 必須是可在目標 Android arm64 環境 執行的版本。要明確指定用哪一份 Bun、而不是交給 `PATH` 決定，見 [指定要封裝進應用的執行檔](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E6%8C%87%E5%AE%9A%E8%A6%81%E5%B0%81%E8%A3%9D%E9%80%B2%E6%87%89%E7%94%A8%E7%9A%84%E5%9F%B7%E8%A1%8C%E6%AA%94)的 `-b`/`--bun-bin`。
    

> \[!IMPORTANT\] 這個 `which bun` 只在 `libbun.so` **不存在時**跑一次。複製過去之後， 之後每次建置都直接用根目錄那一份，不會再看 `PATH`——所以你後來 `bun upgrade`、裝了新版、或切換到別的 Bun，建出來的 APK 裡還是舊的那顆。`5b` 印出的 revision 就是拿來確認這件事的。 要換成 `PATH` 上現在這顆，明確指定一次：
> 
> ```sh
> npx @drxiaozhi/minapk -b "$(which bun)"
> ```

-   封裝原生函式庫那一步（`5b`）會執行 `libbun.so --revision` 並印出結果， 讓你知道這顆 APK 裡到底裝的是哪個版本的 Bun。因為 `libbun.so` 是 Android arm64 執行檔，不見得能在建置用的機器上直接跑；跑不起來時只會印 `unknown (not runnable on this build host)`，不會中斷建置。

清除 `build/` 暫存資料夾：

```sh
npm run clean
# 等同
bun ./clean.js
```

## 進階設定

### 完全取代封裝進應用的設定檔

```sh
npx @drxiaozhi/minapk /path/to/your.elf --config /path/to/package.json
```

`--config` 指到的檔案會**完全取代**封裝進 APK 的 Buninu payload 裡的 `package.json`（是整份換掉，不是合併），可以用來自訂 `buninu.shell`、`buninu.command`、`buninu.exitAfterCmd` 等啟動設定，而不用去修改 `no_backup` 裡的原始檔案。

運作方式：

1.  跟 `build.sh` 自己內部的做法一樣，先從本地 `no_backup` 匯出一份 `buninu.tgz`（沒有 `no_backup` 則詢問是否 `npx buninu@latest --export`）。
2.  把 `--config` 檔案的內容原封不動 append 成 tar 裡第二個同路徑的 `<頂層目錄>/package.json` entry，寫在既有內容的尾端——不重新打包整個 tgz，其餘上千筆 entry（含 symlink、可執行權限）完全不動。tar 解壓時後面的 entry 會覆蓋前面的，所以 App 實際解出來的就是 `--config` 提供的那份。
3.  把處理好的 tgz 路徑傳給 `build.sh`，跳過它自己的 export 步驟。

> \[!IMPORTANT\] `--config` 檔案必須是**完整**的 `package.json`（含 `name`/`version`/`scripts`/`bin` 等欄位），不是只寫 `buninu` 那一段，因為是整份取代、不是合併。可以先用 `npx buninu@latest --export-config` 產生一份完整的 `buninu.json` 當起點，直接拿來改 `buninu` 區段後當 `--config` 的輸入即可。

### 指定應用內起始命令

不想為了改一個欄位就手寫一份完整 `package.json`，可以只用這個旗標：

```sh
npx @drxiaozhi/minapk /path/to/your.elf -c "echo custom startup command"
```

`-c`/`--command <command>` 把封裝進 APK 的 `package.json` 裡的 `buninu.command` **整個欄位**覆蓋成這個字串（`buninu` 本身也接受 `"command": "字串"` 這種簡寫，等同套用到所有平台，minapk 只建置 Android 所以不用管 `default`/`android`/`linux` 這些子欄位怎麼合併）。

> \[!WARNING\] Buninu 預設的 `buninu.command.android` 是：
> 
> ```sh
> if command -v libmain.so >/dev/null 2>&1; then libmain.so; else printf ...; fi
> ```
> 
> 也就是偵測到 `libmain.so` 就自動啟動它。用 `-c` 是**整個欄位覆蓋**，不是在這段邏輯上加東西，所以你的 elf（透過 elf 位置參數打包成 `libmain.so` 的那個）**不會自動被執行**，除非你自訂的 `command` 裡自己有呼叫 `libmain.so`（例如 `-c "libmain.so"` 或包在你自己的邏輯裡）。忘記這件事最常見的症狀就是：APK 建置成功、App 也能開，但你的程式完全沒有啟動。

`-c` 可以跟 `--config` **合併使用**而不是互斥：

-   有帶 `--config`：以 `--config` 檔案的內容當底，`-c` 只覆蓋其中的 `buninu.command`，其他欄位維持 `--config` 檔案原樣。
-   沒帶 `--config`：以本次 export 出來、Buninu payload 裡原本的 `package.json` 當底，一樣只覆蓋 `buninu.command`，其餘欄位維持原樣，不需要另外準備 `--config` 檔案。

### 停用-指令跑完掉回互動式指令行

```sh
npx @drxiaozhi/minapk /path/to/your.elf -c "echo custom command" --no-shell
```

`--no-shell` 不用帶值，出現就把 `buninu.exitAfterCmd` 設成 `true`（預設 `false`，見 [Buninu README](https://www.npmjs.com/package/buninu) 的 `exitAfterCmd` 說明）：`buninu.command` 執行完後直接結束，不會像預設那樣掉回互動式 shell。合併規則跟 `-c` 一樣——有 `--config` 就疊加在它上面，沒有就疊加在本次 export 出來的原始 `package.json` 上，其餘欄位都不動。

### 返回鍵直接離開應用

```sh
npx @drxiaozhi/minapk /path/to/your.elf --no-back-to-console
```

`--no-back-to-console` 不用帶值，出現就把 `buninu.backToConsole` 設成 `false`（預設 `true`）：在 app WebView 而且它自己沒有上一頁可回時，按返回鍵會走離開 App 那條路，而不是切回 console WebView。「離開」在哪個 WebView 按都一樣要先過確認對話框——這個欄位只決定要不要先繞去 console，不會讓返回鍵變成不問就直接退出。合併規則跟 `-c`／`--no-shell` 完全一樣——有 `--config` 就疊加在它上面，沒有就疊加在本次 export 出來的原始 `package.json` 上，其餘欄位都不動。

這個欄位是**由 Android 端的 App 讀的**，不是 Buninu 自己讀的，所以在 APK 以外的地方設它不會有任何效果。App 端讀不到檔案、JSON 壞掉、沒有這個欄位、值不是布林，一律當成 `true`（回到 console），不會因此丟出任何錯誤。

### 預設隱藏螢幕上額外按鍵列

```sh
npx @drxiaozhi/minapk /path/to/your.elf --hide-extra-keys
```

`--hide-extra-keys` 不用帶值，出現就把 `buninu.hideExtraKeys` 設成 `true`（預設 `false`）。App 啟動時會隱藏額外按鍵列，之後仍可從音量鍵 選單正常切換顯示。它與 `-c`、`--no-shell`、`--no-back-to-console` 和 `--config` 的合併規則相同，其餘欄位都不動。

### 指定要封裝進應用的執行檔

```sh
npx @drxiaozhi/minapk /path/to/your.elf -b /path/to/android-arm64/bun
```

`-b`/`--bun-bin <path>` 把指定的 Bun 執行檔複製成專案根目錄的 `libbun.so`， **取代**目前那一份，然後才開始建置。用來在不同版本的 Bun（例如 canary 與 stable）之間切換，而不必自己去 `cp` 或去動 `PATH`。該 Bun 一樣必須是可在 Android arm64 上執行的版本。

> \[!IMPORTANT\] 這個旗標跟 `-n`/`-p`/elf 位置參數**不一樣**：那三個只影響當次建置，磁碟上的 `appname.txt`/`pkgname.txt`/`libmain.so` 永遠不會被寫入；`-b` 則是真的把 `libbun.so` 覆蓋掉，之後不帶 `-b` 的建置也會繼續用這一份。
> 
> 這是刻意的：`libbun.so` 本來就不是 checked-in 的檔案，而是 `build.sh` 自己 會寫的快取（不存在時它會把 `which bun` 找到的 Bun 複製過去），所以 `-b` 覆蓋掉的只是「上次從 `PATH` 撿來的那份」，沒有什麼可預期的預設值會被破壞。

不論有沒有帶 `-b`，建置到 `5b` 那一步都會印出實際封裝進去的 Bun 版本：

```text
5b. Packaging native libraries...
Packaged Bun (libbun.so) revision:
  1.4.0-canary.1+41c3f6fdb
```

`-c`/`--no-shell`/`--no-back-to-console`/`--config`/`-b` 可以跟 [`-n`](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E6%87%89%E7%94%A8%E7%A8%8B%E5%BC%8F%E5%90%8D%E7%A8%B1%E8%88%87%E8%BC%B8%E5%87%BA%E6%AA%94%E6%A1%88%E5%90%8D%E7%A8%B1)／[`-p`](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%AE%89%E5%8D%93%E5%A5%97%E4%BB%B6%E5%90%8D%E7%A8%B1)以及 elf 位置參數任意組合，例如：

```sh
npx @drxiaozhi/minapk /path/to/your.elf -n MyApp -p com.example.myapp -c "echo hello" --no-shell
```

## 只更新幫你牛使用者空間

完成至少一次完整建置並已有根目錄 APK 後，可以執行：

```sh
./repack.sh
```

`repack.sh` 不會重新編譯 Android 資源、Java 或 DEX。它會：

1.  以根目錄的 `<appname>.apk` 為來源。
2.  重新匯出 `buninu.tgz` 並產生 `buninu.stamp`。
3.  替換 APK 內的 payload。
4.  重新執行 zipalign 與簽章。
5.  在根目錄輸出 `<appname>r.apk`，不覆蓋原始 APK。

例如：

```text
Hello2.apk → Hello2r.apk
```

也可以明確使用 Android system shell：

```sh
/system/bin/sh ./repack.sh
```

## 幫你牛使用者空間來源

Buninu npm 套件：[https://www.npmjs.com/package/buninu](https://www.npmjs.com/package/buninu)

Buninu GitHub 原始碼：[https://github.com/jjtseng93/buninu](https://github.com/jjtseng93/buninu)

> \[!IMPORTANT\] `buninu.tgz` **必須只有一個頂層資料夾**。App 解壓時會移除第一層 （`--strip-components=1`），再把其內容直接放入 Buninu home。

解壓縮的目的地是 Android App 的內部私有 Buninu home：

```text
/data/data/<package-name>/no_backup
```

Android 在部分版本可能將同一個 App data directory 表示為 `/data/user/0/<package-name>`；實際路徑由 `ApplicationInfo.dataDir` 取得。 壓縮檔的單一頂層只是可移除的包裝層，不會在 `no_backup` 裡再多建立一層。

正確：

```text
no_backup/
no_backup/bin/
no_backup/apps/
```

錯誤：

```text
no_backup/
other_directory/
```

頂層資料夾的名稱不限定為 `no_backup`，但整份 archive 中只能有一個頂層名稱。若首次安裝時不符合此要求，App 無法建立 Buninu home，WebView 會顯示中英文錯誤訊息；已有舊安裝時則會跳過該 payload 並啟動既有版本。

若根目錄存在 `no_backup`，兩支腳本會使用：

```sh
bun no_backup/bin/init.js --export buninu.tgz
```

若不存在，腳本會詢問是否執行 `npx buninu@latest --export`。只有明確輸入 `y` 或 `Y` 才會從 npm 匯出。

## User Manual: Inside the APK

這一區說明 APK 安裝並開啟後，使用者可以直接操作的功能。Buninu shell 內建指令的完整說明請見 Buninu 的 [Commands inside the shell](https://github.com/jjtseng93/buninu#commands-inside-the-shell)。

### 螢幕上額外按鍵列

建出來的 App 底部有一排終端機常用按鍵，很多鍵短按跟長按是不同功能：

| 按鍵 | 短按 | 長按 |
| --- | --- | --- |
| ESC | Esc | Ctrl+Q |
| SHFT | 切換 Shift 修飾鍵 | **Ctrl+D** |
| ^C x | Ctrl+C | Ctrl+X |
| HOME | Home | Ctrl+U |
| END | End | Ctrl+K |
| TAB | Tab | Shift+Tab |
| PGU | Page Up | 按住持續向上捲動（滑鼠滾輪） |
| PGD | Page Down | 按住持續向下捲動（滑鼠滾輪） |
| ↑ ↓ ← → | 方向鍵 | 按住連續輸入 |
| Ent | Enter | Forward Delete |
| CTRL | 切換 Ctrl 修飾鍵 | （無） |
| ALT | 切換 Alt 修飾鍵 | 切換 Super（Meta）修飾鍵 |

CTRL、ALT、SHFT 是 Termux 風格的一次性（one-shot）修飾鍵：短按後按鈕會反白表示已啟用，套用到下一個按下的按鍵之後就會自動清除，所以要打 Ctrl+C 只要先點 CTRL 再點 `^C x`（或任何字母鍵），不需要多點觸控同時按住兩個鍵。畫面右上角還有一個很窄的隱形輸入框，可以喚出系統輸入法直接打字/貼上文字。

ALT 按鈕的短按與長按各自獨立切換 Alt／Super：只開啟 Super 時顯示 `SUP`，兩者都開啟時顯示 `SUA`，有啟用修飾鍵時會反白。可再搭配 CTRL／SHFT，等待下一個按鍵送出後一起清除並恢復 `ALT`。例如長按、短按，再按 M，便會送出 Alt+Super+M。Super 透過原生 Android Meta 按鍵事件送給 WebView；部分 WebView 會遺失網頁事件的 `metaKey`，需由 jsgotty 等接收端補償，終端機內的程式也需支援相應鍵盤編碼。

長按 PGU／PGD 會連續送出滑鼠滾輪事件，放開即停止；短按仍是 Page Up／Page Down。使用方式：

-   先觸碰網頁中想捲動的區域，再長按 PGU（向上）或 PGD（向下）。滾輪送到該 WebView 最後一次範圍內的單指觸控位置，按底下的按鍵列不會覆蓋它；尚未觸碰時才使用中心點。
-   如需水平捲動，先觸碰可水平捲動的區域，再點 SHFT、長按 PGU／PGD。這會送出 Shift＋滾輪；是否轉成左右捲動由接收程式決定，例如 GPUI 的捲動範例支援此操作。
-   CTRL、ALT、SHFT、Super 可組合套用到整段長按。開始連發時會清除按鈕的啟用狀態，但每次滾輪仍保留當時的修飾鍵；下一次長按需重新選取。終端滑鼠協定只能表達 Shift／Alt／Ctrl，Super 不保證能穿過終端機傳遞。
-   每個 WebView 分別記錄位置。旋轉螢幕、顯示輸入法或改變尺寸後，送出前會把座標限制在目前 WebView 範圍內。記住的是畫面位置，不是網頁元素；頁面捲動或重新排版後，可再觸碰一次重新指定。

Ent、TAB、PGU／PGD、方向鍵等送鍵按鈕只把焦點交回 WebView 並送出按鍵，不主動叫出螢幕鍵盤，也不強制收起已開啟的鍵盤。修飾鍵仍會叫出輸入法以等待字母輸入。

### 音量鍵上選單

實體**音量鍵 +** 會攔截下來（不會真的調音量），改成跳出一個小選單：切換螢幕按鍵列、在 WebView 裡 eval JS、選取終端機文字、上一頁／下一頁、跳到指定網址、縮放、Eruda console、背景權限設定、切換 WebView（直接切到下一個，不再多一層選單；項目本身會標出要切去哪一個，例如 `Switch WebView → 1: app`）。

### WebViews

App 裡有兩個 WebView，從啟動就都存在、不會被建立或關閉：`0` 是 console（Buninu 起的 jsgotty 終端機），`1` 是 app WebView，一開始是空白的、擺在後面。按鍵列、音量鍵選單、返回鍵一律作用在**當前在前景的那一個** WebView 上，所以切換 WebView 就等於同時把這三者換過去。在 app WebView 沒有上一頁可回時按返回鍵，會切回 console 而不是結束 App——沒有任何東西被關掉，離開前景的那個 WebView 照樣繼續跑（這個行為由 `buninu.backToConsole` 決定，預設 `true`，見 `--no-back-to-console`）。切換的方式是音量鍵選單最後那個「Switch WebView →」項目（按下去就直接切，不會再問你要哪一個），或從 Buninu 裡呼叫下面的 `showWebView`。

### 網頁全螢幕

支援前景 WebView 的網頁／影片全螢幕請求。進入後隱藏按鍵列與系統列，返回鍵會優先退出全螢幕，恢復原介面及按鍵列顯示狀態；切換 WebView、頁面導航或 Activity 結束時也會退出。不強制切換橫向，不影響原有檔案選擇器。是否能發起請求仍取決於網站、WebView 版本及使用者操作；iframe 也需網站允許全螢幕。

手動驗證頁在 `tests/fullscreen.html`。可於專案目錄執行 `python3 -m http.server 8765 --bind 127.0.0.1 --directory tests`，再用 App 開啟 `http://127.0.0.1:8765/fullscreen.html`。分別確認網頁全螢幕、影片控制列全螢幕、返回鍵、切換 WebView，以及原先隱藏的按鍵列退出後仍保持隱藏。

### 返回鍵

返回鍵真的會離開 App 的那一步（console 也沒有上一頁可回時）會先跳出確認對話框。確認離開之後不只是關掉畫面：Buninu 行程、native bridge 的 socket 都會收掉，整個 App 行程結束，下次開啟是全新的一份。Buninu 是用 `bun --no-orphans` 啟動的，所以它自己 spawn 出去的 jsgotty、shell 也會跟著一起結束，不會留下孤兒行程；同一個旗標也讓 Buninu 在 App 行程被系統殺掉時自行退出。

### 原生橋

App 內建一座從 Buninu 通到 Android 原生層的橋（`no_backup/apps/native-bridge`），透過 `MainActivity` 開的一個 unix socket，把 Toast 與系統剪貼簿讀寫暴露給 Buninu 裡跑的 Bun 行程。Buninu 隨附的 `xclip` 指令（`apps/xclip`）就是建在這座橋上：

```sh
echo hello | xclip -selection clipboard   # 寫進 Android 系統剪貼簿
xclip -o -selection clipboard             # 讀出來
```

`-selection primary`（不帶 `-selection` 時的預設值）維持純本地檔案、不碰原生剪貼簿，對應真正 X11 的語意；只有 `-selection clipboard`/`-clip` 才會透過 native-bridge 走到 Android 系統剪貼簿。jsmdcui 的剪貼簿後端偵測本來就會在偵測到 `xclip` 時使用它，所以 jsmdcui 裡的滑鼠中鍵貼上、選取文字自動同步、`PastePrimary` 指令，在這個 App 裡不需要額外設定就能動作。

要直接呼叫這座橋、不透過 `xclip`，可以在 `js back` 區塊裡 `import { toast, clipboardRead, clipboardWrite } from` 該路徑；每次呼叫預設 5 秒逾時，Android 端沒有回應也不會卡住呼叫端。詳見 `no_backup/README.md` 的「Commands inside the shell」一節。

`xdg-open` 在 APK 裡預設是把 URL 丟給系統的預設處理程式（等於離開 App），設 `MINAPK_WEBVIEW=<id>` 就改成載進那個 WebView 並直接切到前景：

```sh
MINAPK_WEBVIEW=1 xdg-open https://example.com   # 開在 app WebView 並顯示
export MINAPK_WEBVIEW=1                         # 或整個 session 都這樣
```

`0` 是 console，會把終端機那頁導走（返回鍵可以回去、jsgotty 會重連，但通常不是你要的）；`-1` 是當前前景那個。只有 URL 會被導向，檔案路徑一律照舊走系統處理程式——WebView 從 API 30 起 `setAllowFileAccess` 預設 false，`file://` 讀不到 Buninu home 底下的檔案，而原生端本來就有 content:// provider 在服務同一個檔案。值不是純整數會在 stderr 提醒並當成沒設（不會亂猜），沒設或空字串維持原行為，指到不存在的 WebView 則印出錯誤後退回系統處理程式。

同一座橋也把上面那兩個 WebView 交給 Buninu 控制，共四個 function：`openWebView(id, url)`、`evalWebView(id, js)`、`showWebView(id)`、`currWebView()`。id 給 `-1` 代表「目前在前景的那一個」。

四個都各有一個短名 `openwv`／`evalwv`／`showwv`／`currwv`，跟剪貼簿的 `getcb`／`setcb` 同一套做法：Java 端兩種拼法都收，`_discover` 也兩種都列，所以 CLI、`rpcraw`、`import` 三種用法都通。

```sh
native-bridge openwv 1 https://example.com   # 載入，但畫面不動
native-bridge showwv 1                       # 這時候才切到前景
native-bridge evalwv 1 document.title
native-bridge currwv
```

`openWebView` **只載入、不切到前景**，所以「使用者還在看終端機，背景先把 app WebView 的頁面載好」是一次呼叫就做完的事；`showWebView` 才是唯一會改變畫面的那個，`showWebView -1` 不是空操作而是切到下一個（只有兩個 WebView 時就是來回切換）。`evalWebView` 回傳的是運算式真正的值（數字/字串/物件），不是包成字串的值；`undefined`、function、丟出例外都會變成 `null`，因為 WebView 本身就分不出這三者。同樣的四個 function 也能 `import` 進 `js back` 區塊用（另外還有 `WEBVIEW_CURRENT`／`WEBVIEW_CONSOLE`／`WEBVIEW_APP` 三個常數）。

同一座橋也接了語音朗讀：`tts "hello"` 會唸出文字並等講完才結束，`-a` 不等直接返回。沒有 App 可用時會退回 `espeak-ng`/`say`/PowerShell 等桌面平台指令，一樣可以用。

### 可選外部工具

`bunproot` 是使用者執行 `bunx` 後自行下載的可選工具，採用 GPL-2.0-or-later；`js-udocker` 採用 Apache-2.0。兩者都不隨 minapk 或產生的 APK 一起散布。

```sh
bunx bunproot --git clone https://github.com/jjtseng93/js-udocker
cd js-udocker
export JS_UDOCKER_BUNPROOT=$HOME/.bun/bin/bunproot
bun udocker.js run --name=ap alpine
# bun udocker.js ps
```

## 主要外部工具

-   Bun
-   aapt2
-   zipalign
-   zip
-   Java
-   keytool（只有建立 keystore 時需要）

其他建置用 JAR 位於 `tools/`。

## 應用簽章與金鑰工具

`build.sh` 與 `repack.sh` 預設使用：

```text
tools/debug.keystore
```

如果這個檔案不存在，腳本才會呼叫 `keytool` 自動建立 debug keystore。之後的 build 與 repack 會持續使用同一個檔案簽章，因此更新已安裝的 APK 時請保留它。

專案本身隨附一份現成的 `tools/debug.keystore`，刻意讓沒有 `keytool`（或整個 Java 環境）的環境也能完整跑完簽章這一步——末日生存情境下，能建置出可安裝的 APK 比什麼都重要。

> \[!WARNING\] 隨附的 `tools/debug.keystore` 是**所有沒有換掉它的使用者共用同一把私鑰**（密碼固定是 `android`），因為它透過 npm 公開發佈，任何人都拿得到。這代表：
> 
> -   用預設 keystore 簽出來的 APK，跟其他人用同一份預設 keystore 簽出來的 APK，是同一把金鑰簽的。
> -   只要 package name 相同，任何人都能用這把公開金鑰重新簽署別的 APK，Android 會把它當成合法更新接受安裝。
> 
> 只要環境裡有 `keytool`，刪掉 `tools/debug.keystore` 再重新建置一次，腳本就會自動幫你產生一把只有你自己有的新金鑰。正式發佈或給別人安裝之前，請務必這樣做一次，或改用你自己的 release keystore 並妥善備份私鑰與密碼。

## 產生單一可執行檔

以下兩條路徑都會產生 minapk 要的那種 elf——以 `/system/bin/linker64` 為載入器的 arm64 執行檔。兩者都需要 Bun 1.4 以上，用最新的 1.4 正式版就可以，不需要 canary：

```sh
bun upgrade
```

### 途徑一-一行建置

`hlw.js`：

```js
console.log("Hello from Bun single-file executable")
```

```sh
bun build --format=esm --compile --minify --bytecode ./hlw.js
npx @drxiaozhi/minapk hlw
```

得到 `hlw.apk`。輸出檔名不用指定，會自動去掉副檔名變成 `hlw`（Windows 上則是 `hlw.exe`）。

### 途徑二-標記語言應用

`hlw.md`：

````markdown
#!/usr/bin/env jsmdcui

## Question 問題
- What is 1+2+3+4+..+..+∞

```text#ans
-1/12
```
- [Submit 提交](javascript:checkAns())
- [Where am I? 我在哪？](javascript:whereAmI())

```js front
export function checkAns()
{
  if($('#ans').val().trim()=='-1/12')
    $('#ans').val('答對🥳Right!');
  else
    $('#ans').val('答錯😫Wrong!');
}

export async function whereAmI()
{
  const r = await rpc.sysinfo();
  alert(Object.entries(r).map(([k, v]) => `${k}: ${v}`).join('\n'));
}
```

```js back
export function sysinfo()
{
  return {
    bun: Bun.version,
    platform: process.platform,
    arch: process.arch,
    buninu: process.env.BUNINU_HOME ?? '(not under Buninu)',
    android: process.env.PKG_DDIR ?? '(not inside an APK)',
  };
}
```
````

```sh
npx jsmdcui --build-md-exe hlw.md
npx @drxiaozhi/minapk mdcui
```

> \[!NOTE\] 輸出的執行檔固定叫 `mdcui`，不是 `hlw`，所以直接餵給 minapk 會得到 `mdcui.apk`。要別的名字就先 `mv`。（不要試圖用 `--outfile` 指定：它不是相對你的 cwd 解析的，檔案會跑到 jsmdcui 自己的目錄裡去。）

`checkAns` 是純前端的答案比對；`whereAmI` 則透過 `await rpc.sysinfo()` 呼叫到 `js back` 區塊，回報這個 app 現在跑在哪裡。同一個執行檔在 Termux 裡直接跑會顯示 `(not inside an APK)`，包成 APK 裝起來之後，同樣那顆按鈕就會顯示 Buninu home 與 Android app 私有目錄的實際路徑。

## Google Play

> \[!IMPORTANT\] minapk 建出來的 APK **不是一個可以直接上架的成品**。以下兩點是機制上的硬限制，跟政策解讀無關，照現況直接丟上 Play Console 就會被擋下來：
> 
> -   **格式**：新 App 自 2021 年 8 月起只收 [Android App Bundle（AAB）](https://developer.android.com/guide/app-bundle/faq)，minapk 產出的是 APK。
> -   **簽章**：新 App 一律走 Play App Signing，你手上只留 upload key，不可能沿用上方「APK 簽章與 keytool」提到、所有人共用的那份 `tools/debug.keystore`。
> 
> 換句話說，要上架至少得自行改用 AAB 流程、換成自己的金鑰。這些都做完之後，才輪到下面比較沒有標準答案的政策問題。

### 政策面

Play 的 [Device and Network Abuse](https://support.google.com/googleplay/android-developer/answer/16559646) 政策規定：App 不得從 Google Play 以外的來源下載可執行碼（dex、JAR、`.so`），也不得用 Play 更新機制以外的方式更新自己；但「在虛擬機或直譯器中執行的程式碼」不在此限。

minapk 建出來的 APK 目前是這樣執行的：

-   `libbun.so`／`libmain.so` 都**隨 APK 打包**在 `lib/arm64-v8a/`，App 以 `ProcessBuilder` 從 `nativeLibraryDir` 執行。該目錄是 Android 10（API 29）W^X 限制下少數仍保有執行權限的位置。
-   Buninu payload（JS）同樣**隨 APK 打包**在 assets，首次啟動才解壓到 `no_backup`，再由上面那個 `libbun.so` 當直譯器執行。
-   整個流程不會從網路下載任何原生可執行檔。

類似專案的做法對照：

-   `libnode.so`（[nodejs-mobile](https://github.com/JaneaSystems/nodejs-mobile)）走更保守的路線——Node 編譯成真正的 JNI 共享函式庫，用 `System.loadLibrary("node")` 載入到 App 自己的行程內，不 fork/exec 子行程，所以它單純就是「APK 內的原生函式庫」。
-   minapk 是把 Bun 當獨立執行檔 exec，比較接近 [Termux 在 Play 上的版本](https://github.com/termux-play-store)（該版本以 `system_linker_exec` 處理 Android 10+ 的 W^X 限制）。Termux 本身曾因 target API 29 的執行限制長期無法在 Play 更新，後來才以這個分支回到 Play。

仍需自行評估的部分：預設會開一個互動式 shell，使用者可以在裡面執行任意程式；Buninu 的 `bunx` 會在執行期從 npm 安裝並執行套件。這類「執行期才取得程式碼」的行為，正是審核最容易被盯上的地方。

> \[!NOTE\] 我本身不是法律專業，以上只是對照公開政策條文與類似專案做法的整理，不構成法律或合規建議。目前已提供 `--no-shell`（即 `buninu.exitAfterCmd`）讓指令跑完就直接結束、不落回互動式 shell，作為縮小暴露面的選項。實際的上架審核規範與結果，有待各位使用者自行發掘。

## 未來規劃

-   修復xterm終端機捲動問題
-   ~加入Ctrl Alt Shift等按鍵~（已完成）
-   一直包裝直到做到
    -   ~npx @drxiaozhi/minapk your\_binary~（已完成，見「建置」）
    -   npx @drxiaozhi/minapk myapp.md
-   ~原生 bridge（架構還在設計中）~（已完成：`native-bridge`／`xclip`／`tts`，目前有 toast、剪貼簿、TTS；缺的是繼續暴露更多 Android 能力，見下）
-   `BUN_BE_BUN` 機制：`libmain.so` 本質上是「Bun 執行檔本體 + 附加上去的 standalone module graph」（`bun build --compile` 的輸出），正常執行會直接偵測並啟動內嵌的 app。Bun 官方文件（single-file executable）記載了 `BUN_BE_BUN=1` 這個環境變數，設定後同一個檔案會改成表現得像單純的 `bun` CLI、跳過 standalone graph 偵測。理論上可以拿 `libmain.so` 兼職當作 `libbun.so` 用（呼叫時帶 `BUN_BE_BUN=1`），不用再額外打包一份完整 Bun 執行檔，省下可觀的 APK 空間。架構還沒定案——目前 `libbun.so`／`libmain.so` 各自一份的好處是彼此可以互相 fallback（例如 `libmain.so` 的 standalone graph 或 `BUN_BE_BUN` 行為出狀況時還有獨立的 `libbun.so` 可用），改成共用一份就要想清楚失去這層保險的取捨

## 目錄

-   [安裝依賴](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%AE%89%E8%A3%9D%E4%BE%9D%E8%B3%B4)
    -   [Termux](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-termux)
    -   [Debian / Ubuntu arm64 inside Termux proot](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-debianubuntu-arm64-inside-termux-proot)
-   [兩分鐘快速開始-你好世界專案](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%85%A9%E5%88%86%E9%90%98%E5%BF%AB%E9%80%9F%E9%96%8B%E5%A7%8B-%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C%E5%B0%88%E6%A1%88)
-   [應用程式名稱與輸出檔案名稱](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E6%87%89%E7%94%A8%E7%A8%8B%E5%BC%8F%E5%90%8D%E7%A8%B1%E8%88%87%E8%BC%B8%E5%87%BA%E6%AA%94%E6%A1%88%E5%90%8D%E7%A8%B1)
-   [安卓套件名稱](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%AE%89%E5%8D%93%E5%A5%97%E4%BB%B6%E5%90%8D%E7%A8%B1)
-   [建置步驟](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%BB%BA%E7%BD%AE%E6%AD%A5%E9%A9%9F)
-   [進階設定](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E9%80%B2%E9%9A%8E%E8%A8%AD%E5%AE%9A)
    -   [完全取代封裝進應用的設定檔](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%AE%8C%E5%85%A8%E5%8F%96%E4%BB%A3%E5%B0%81%E8%A3%9D%E9%80%B2%E6%87%89%E7%94%A8%E7%9A%84%E8%A8%AD%E5%AE%9A%E6%AA%94)
    -   [指定應用內起始命令](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E6%8C%87%E5%AE%9A%E6%87%89%E7%94%A8%E5%85%A7%E8%B5%B7%E5%A7%8B%E5%91%BD%E4%BB%A4)
    -   [停用-指令跑完掉回互動式指令行](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%81%9C%E7%94%A8-%E6%8C%87%E4%BB%A4%E8%B7%91%E5%AE%8C%E6%8E%89%E5%9B%9E%E4%BA%92%E5%8B%95%E5%BC%8F%E6%8C%87%E4%BB%A4%E8%A1%8C)
    -   [返回鍵直接離開應用](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E8%BF%94%E5%9B%9E%E9%8D%B5%E7%9B%B4%E6%8E%A5%E9%9B%A2%E9%96%8B%E6%87%89%E7%94%A8)
    -   [預設隱藏螢幕上額外按鍵列](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E9%A0%90%E8%A8%AD%E9%9A%B1%E8%97%8F%E8%9E%A2%E5%B9%95%E4%B8%8A%E9%A1%8D%E5%A4%96%E6%8C%89%E9%8D%B5%E5%88%97)
    -   [指定要封裝進應用的執行檔](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E6%8C%87%E5%AE%9A%E8%A6%81%E5%B0%81%E8%A3%9D%E9%80%B2%E6%87%89%E7%94%A8%E7%9A%84%E5%9F%B7%E8%A1%8C%E6%AA%94)
-   [只更新幫你牛使用者空間](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%8F%AA%E6%9B%B4%E6%96%B0%E5%B9%AB%E4%BD%A0%E7%89%9B%E4%BD%BF%E7%94%A8%E8%80%85%E7%A9%BA%E9%96%93)
-   [幫你牛使用者空間來源](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%B9%AB%E4%BD%A0%E7%89%9B%E4%BD%BF%E7%94%A8%E8%80%85%E7%A9%BA%E9%96%93%E4%BE%86%E6%BA%90)
-   [User Manual: Inside the APK](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-user-manual-inside-the-apk)
    -   [螢幕上額外按鍵列](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E8%9E%A2%E5%B9%95%E4%B8%8A%E9%A1%8D%E5%A4%96%E6%8C%89%E9%8D%B5%E5%88%97)
    -   [音量鍵上選單](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E9%9F%B3%E9%87%8F%E9%8D%B5%E4%B8%8A%E9%81%B8%E5%96%AE)
    -   [WebViews](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-webviews)
    -   [返回鍵](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E8%BF%94%E5%9B%9E%E9%8D%B5)
    -   [原生橋](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%8E%9F%E7%94%9F%E6%A9%8B)
    -   [可選外部工具](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E5%8F%AF%E9%81%B8%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7)
-   [主要外部工具](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E4%B8%BB%E8%A6%81%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7)
-   [應用簽章與金鑰工具](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E6%87%89%E7%94%A8%E7%B0%BD%E7%AB%A0%E8%88%87%E9%87%91%E9%91%B0%E5%B7%A5%E5%85%B7)
-   [產生單一可執行檔](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E7%94%A2%E7%94%9F%E5%96%AE%E4%B8%80%E5%8F%AF%E5%9F%B7%E8%A1%8C%E6%AA%94)
    -   [途徑一-一行建置](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E9%80%94%E5%BE%91%E4%B8%80-%E4%B8%80%E8%A1%8C%E5%BB%BA%E7%BD%AE)
    -   [途徑二-標記語言應用](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E9%80%94%E5%BE%91%E4%BA%8C-%E6%A8%99%E8%A8%98%E8%AA%9E%E8%A8%80%E6%87%89%E7%94%A8)
-   [Google Play 上架與政策](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-google-play)
    -   [政策面](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E6%94%BF%E7%AD%96%E9%9D%A2)
-   [未來規劃](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-%E6%9C%AA%E4%BE%86%E8%A6%8F%E5%8A%83)
-   [License](https://npmcn.com/package/%40drxiaozhi/minapk/#readme-license)

## License

本專案依照 [MIT License](https://npmcn.com/package/%40drxiaozhi/minapk/LICENSE) 發布。原始上游專案為 [tinyapk-lab](https://github.com/Promastergame/tinyapk-lab)。Android SDK、建置工具及其他第三方元件各自適用其原有授權；完整工具版本、來源與授權對照請見 [NOTICE](https://npmcn.com/package/%40drxiaozhi/minapk/NOTICE) 及 [LICENSES](https://npmcn.com/package/%40drxiaozhi/minapk/LICENSES/README.md)。

## 常见问题

### @drxiaozhi/minapk 是什么？

从 Bun 单文件可执行文件或源代码构建 Android APK

### 如何安装 @drxiaozhi/minapk？

在项目目录运行 npm install @drxiaozhi/minapk，或使用 pnpm add / yarn add / bun add。它还提供命令行工具 minapk，可以用 npx minapk 直接运行。

### @drxiaozhi/minapk 支持 TypeScript 吗？

该包的 package\.json 没有声明类型入口。需要类型提示时，可以在 npm 上查找社区维护的 @types/drxiaozhi\_\_minapk 是否存在。

### @drxiaozhi/minapk 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @drxiaozhi/minapk 可以商用吗？

@drxiaozhi/minapk 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @drxiaozhi/minapk 的下载量有多少？

根据 npm 官方下载统计，@drxiaozhi/minapk 在 2026/10/13 – 2026/11/11 这 30 天内被下载 4,188 次，日均约 140 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @drxiaozhi/minapk 还在维护吗？

最新版本 0\.5\.1 发布于 2026/10/26（18 天前）；至今共发布 8 个版本，其中过去 12 个月发布了 8 个；首次发布于 2026/08/18。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

