diff --git a/README.md b/README.md
index 1d29bb7..6d603c4 100644
--- a/README.md
+++ b/README.md
@@ -631,6 +631,12 @@ data: [DONE]
## Changelog
+### v2.2.1 (2026-05-19)
+- **Auto-Install Build Tools** — `aapt2`, `ecj`, `d8`, `apksigner` auto-installed via `pkg` with full paths and retry logic
+- **Dev Tools Banner** — yellow warning banner on Coding/Agentic mode if tools missing, one-tap Install button
+- **Bootstrap + Tools in One Tap** — Install button auto-installs Termux bootstrap then build tools sequentially
+- Checks tool availability before every build attempt, auto-installs if missing
+
### v2.2.0 (2026-05-19)
- **Build Pipeline Rewrite** — proven 7-step pipeline tested with Android SDK 36: resources → link → compile (R.java + sources) → DEX → package → sign
- **Auto-Install Build Tools** — `aapt2`, `ecj`, `d8`, `apksigner` auto-installed via `pkg` on first build
diff --git a/android/app/build.gradle b/android/app/build.gradle
index c34e88d..da94c29 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "ai.z.chat"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 13
- versionName "2.2.0"
+ versionCode 14
+ versionName "2.2.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
diff --git a/package.json b/package.json
index ed50b78..e10f0eb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "zai-chat",
- "version": "2.2.0",
+ "version": "2.2.1",
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
"main": "index.js",
"scripts": {
diff --git a/www/css/styles.css b/www/css/styles.css
index 07e625d..edc4fd6 100644
--- a/www/css/styles.css
+++ b/www/css/styles.css
@@ -1107,6 +1107,51 @@ a:hover { text-decoration: underline; }
-webkit-overflow-scrolling: touch;
}
+/* Dev Tools Banner */
+.dev-tools-banner {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 14px;
+ margin: 8px 12px;
+ background: rgba(255, 165, 2, 0.1);
+ border: 1px solid var(--warning);
+ border-radius: var(--radius-sm);
+ font-size: 13px;
+ color: var(--text-primary);
+ animation: fadeIn 0.3s ease;
+}
+.dtb-icon {
+ font-size: 16px;
+ flex-shrink: 0;
+}
+.dtb-msg {
+ flex: 1;
+ line-height: 1.4;
+}
+.dtb-install-btn {
+ background: var(--accent);
+ color: white;
+ border: none;
+ padding: 6px 14px;
+ border-radius: 6px;
+ font-size: 12px;
+ font-weight: 700;
+ cursor: pointer;
+ flex-shrink: 0;
+ white-space: nowrap;
+}
+.dtb-install-btn:disabled { opacity: 0.6; cursor: not-allowed; }
+.dtb-dismiss-btn {
+ background: none;
+ border: none;
+ color: var(--text-muted);
+ font-size: 18px;
+ cursor: pointer;
+ padding: 0 4px;
+ flex-shrink: 0;
+}
+
/* Responsive */
@media (max-width: 480px) {
.message { max-width: 92%; }
diff --git a/www/index.html b/www/index.html
index f7d0ad5..4a3f8c9 100644
--- a/www/index.html
+++ b/www/index.html
@@ -327,13 +327,23 @@
About
-
Z.AI Chat v2.2.0
+
Z.AI Chat v2.2.1
Built with Z.AI SDK & GLM-5.1
Compatible with Android 15/16
Changelog
+ -
+ v2.2.1
+ 2026-05-19
+
+ - Auto-Install Build Tools — aapt2/ecj/d8/apksigner auto-installed via pkg with full paths + retry logic
+ - Dev Tools Banner — warns on Coding/Agentic mode if tools missing, one-tap install button
+ - Bootstrap + Tools in One Tap — Install button auto-installs bootstrap then build tools
+ - Checks tool availability before every build, auto-installs if missing
+
+
-
v2.2.0
2026-05-19
diff --git a/www/js/app.js b/www/js/app.js
index 1e1887f..1e2e951 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -549,6 +549,10 @@
return;
}
+ if ((state.currentMode === 'coding' || state.currentMode === 'agentic') && !termState.devToolsInstalled) {
+ checkDevEnvironment();
+ }
+
if (!state.activeConversationId) {
newConversation();
}
@@ -1901,33 +1905,159 @@
}
async function ensureBuildTools() {
- var check = await shellExec('command -v aapt2 && command -v ecj && (command -v d8 || command -v dx)', termState.homeDir, false);
- if (check.exitCode === 0) return true;
-
- termPrint('[*] Installing build tools...', 'info');
- showStatusToast('Installing build tools (first time)...', 'info');
-
- var installResult = await shellExec(
- 'apt update -y 2>&1 && apt install -y aapt2 ecj dx apksigner 2>&1 || pkg install -y aapt2 ecj dx apksigner 2>&1',
- termState.homeDir, false
- );
-
- if (installResult.output) {
- termPrint(installResult.output.replace(/\n$/, ''), '');
- }
-
- var recheck = await shellExec('command -v aapt2 && command -v ecj', termState.homeDir, false);
- if (recheck.exitCode === 0) {
- termPrint('[OK] Build tools installed', 'success');
- showStatusToast('Build tools installed!', 'success');
+ var check = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1 && (command -v d8 >/dev/null 2>&1 || command -v dx >/dev/null 2>&1)', termState.homeDir, false);
+ if (check.exitCode === 0) {
+ termState.devToolsInstalled = true;
return true;
}
- termPrint('[!] Failed to install build tools automatically', 'err');
- termPrint('Please run manually: pkg install aapt2 ecj dx apksigner', 'warning');
+ if (Shell) {
+ var env = await Shell.getEnv();
+ termState.homeDir = env.HOME;
+ termState.toolsDir = env.TOOLS;
+ termState.projectsDir = env.PROJECTS;
+ termState.cwd = env.CWD || env.HOME;
+ }
+
+ var prefix = termState.homeDir ? termState.homeDir.replace('/home', '') + '/usr' : '';
+ var pkgBin = prefix + '/bin/pkg';
+ var aptBin = prefix + '/bin/apt';
+
+ var pkgExists = await shellExec('test -x "' + pkgBin + '"', termState.homeDir, false);
+ var aptExists = await shellExec('test -x "' + aptBin + '"', termState.homeDir, false);
+
+ if (pkgExists.exitCode !== 0 && aptExists.exitCode !== 0) {
+ termPrint('[!] No package manager found. Install Termux bootstrap first.', 'err');
+ termState.devToolsInstalled = false;
+ return false;
+ }
+
+ termPrint('[*] Installing build tools (aapt2, ecj, dx, apksigner)...', 'info');
+ termPrint('[*] This may take a few minutes on first run...', 'info');
+ showStatusToast('Installing build tools...', 'info');
+
+ var installCmd;
+ if (pkgExists.exitCode === 0) {
+ installCmd = pkgBin + ' update -y 2>&1 && ' + pkgBin + ' install -y aapt2 ecj dx apksigner 2>&1';
+ } else {
+ installCmd = aptBin + ' update -y 2>&1 && ' + aptBin + ' install -y aapt2 ecj dx apksigner 2>&1';
+ }
+
+ var installResult = await shellExec(installCmd, termState.homeDir, false);
+ if (installResult.output) {
+ var out = installResult.output;
+ if (out.length > 2000) out = out.substring(0, 1000) + '\n... truncated ...\n' + out.substring(out.length - 800);
+ termPrint(out.replace(/\n$/, ''), '');
+ }
+
+ var recheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
+ if (recheck.exitCode === 0) {
+ termPrint('[OK] Build tools installed successfully!', 'success');
+ showStatusToast('Build tools installed!', 'success');
+ termState.devToolsInstalled = true;
+ return true;
+ }
+
+ if (pkgExists.exitCode === 0 && installResult.exitCode !== 0) {
+ termPrint('[*] Retrying with apt directly...', 'info');
+ var retryResult = await shellExec(aptBin + ' update 2>&1 && ' + aptBin + ' install -y aapt2 ecj dx apksigner 2>&1', termState.homeDir, false);
+ if (retryResult.output) termPrint(retryResult.output.substring(0, 1500).replace(/\n$/, ''), '');
+
+ var recheck2 = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
+ if (recheck2.exitCode === 0) {
+ termPrint('[OK] Build tools installed!', 'success');
+ showStatusToast('Build tools installed!', 'success');
+ termState.devToolsInstalled = true;
+ return true;
+ }
+ }
+
+ termPrint('[!] Auto-install failed. Open Terminal and run:', 'err');
+ termPrint(' pkg update && pkg install aapt2 ecj dx apksigner', 'warning');
+ termState.devToolsInstalled = false;
return false;
}
+ async function checkDevEnvironment() {
+ if (state.currentMode !== 'coding' && state.currentMode !== 'agentic') return;
+
+ if (!Bootstrap) return;
+ try {
+ var bsStatus = await Bootstrap.getStatus();
+ if (!bsStatus.installed) {
+ showDevToolsBanner('Termux not installed. Tap Dev Setup to install Linux environment + build tools.');
+ return;
+ }
+ } catch(e) {}
+
+ if (termState.devToolsInstalled) return;
+
+ var check = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
+ if (check.exitCode === 0) {
+ termState.devToolsInstalled = true;
+ return;
+ }
+
+ showDevToolsBanner('Build tools (aapt2, ecj, d8) not installed. Tap to auto-install.');
+ }
+
+ function showDevToolsBanner(msg) {
+ var existing = $('#dev-tools-banner');
+ if (existing) existing.remove();
+
+ var container = $('#messages');
+ if (!container) return;
+
+ var banner = document.createElement('div');
+ banner.id = 'dev-tools-banner';
+ banner.className = 'dev-tools-banner';
+ banner.innerHTML = '⚠ ' +
+ '' + msg + '' +
+ '' +
+ '';
+
+ container.insertBefore(banner, container.firstChild);
+
+ banner.querySelector('.dtb-install-btn').addEventListener('click', async function() {
+ var btn = this;
+ btn.textContent = 'Installing...';
+ btn.disabled = true;
+
+ var bsStatus;
+ try { bsStatus = await Bootstrap.getStatus(); } catch(e) { bsStatus = { installed: false }; }
+
+ if (!bsStatus.installed) {
+ try {
+ await Bootstrap.install();
+ if (Shell) {
+ var env = await Shell.getEnv();
+ termState.homeDir = env.HOME;
+ termState.toolsDir = env.TOOLS;
+ termState.projectsDir = env.PROJECTS;
+ termState.cwd = env.CWD || env.HOME;
+ }
+ updateCwdDisplay();
+ } catch(e) {
+ btn.textContent = 'Bootstrap failed';
+ return;
+ }
+ }
+
+ var ok = await ensureBuildTools();
+ if (ok) {
+ banner.remove();
+ showStatusToast('All tools installed!', 'success');
+ } else {
+ btn.textContent = 'Retry';
+ btn.disabled = false;
+ }
+ });
+
+ banner.querySelector('.dtb-dismiss-btn').addEventListener('click', function() {
+ banner.remove();
+ });
+ }
+
async function autoExecuteActions(actions, conv) {
var hasFiles = actions.some(function(a) { return a.type === 'create_file'; });
var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
@@ -2585,6 +2715,7 @@
updateModeSelector();
updateHeader();
updateTerminalVisibility();
+ checkDevEnvironment();
saveState();
});
});