v2.2.1: Auto-install build tools (aapt2/ecj/d8/apksigner), dev tools banner with one-tap install
This commit is contained in:
@@ -631,6 +631,12 @@ data: [DONE]
|
|||||||
|
|
||||||
## Changelog
|
## 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)
|
### 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
|
- **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
|
- **Auto-Install Build Tools** — `aapt2`, `ecj`, `d8`, `apksigner` auto-installed via `pkg` on first build
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "ai.z.chat"
|
applicationId "ai.z.chat"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 13
|
versionCode 14
|
||||||
versionName "2.2.0"
|
versionName "2.2.1"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zai-chat",
|
"name": "zai-chat",
|
||||||
"version": "2.2.0",
|
"version": "2.2.1",
|
||||||
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
|
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1107,6 +1107,51 @@ a:hover { text-decoration: underline; }
|
|||||||
-webkit-overflow-scrolling: touch;
|
-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 */
|
/* Responsive */
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.message { max-width: 92%; }
|
.message { max-width: 92%; }
|
||||||
|
|||||||
@@ -327,13 +327,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>About</h3>
|
<h3>About</h3>
|
||||||
<p class="about-text">Z.AI Chat v2.2.0</p>
|
<p class="about-text">Z.AI Chat v2.2.1</p>
|
||||||
<p class="about-text">Built with Z.AI SDK & GLM-5.1</p>
|
<p class="about-text">Built with Z.AI SDK & GLM-5.1</p>
|
||||||
<p class="about-text">Compatible with Android 15/16</p>
|
<p class="about-text">Compatible with Android 15/16</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Changelog</h3>
|
<h3>Changelog</h3>
|
||||||
<ul class="changelog-list">
|
<ul class="changelog-list">
|
||||||
|
<li>
|
||||||
|
<span class="changelog-version">v2.2.1</span>
|
||||||
|
<span class="changelog-date">2026-05-19</span>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Auto-Install Build Tools</strong> — aapt2/ecj/d8/apksigner auto-installed via pkg with full paths + retry logic</li>
|
||||||
|
<li><strong>Dev Tools Banner</strong> — warns on Coding/Agentic mode if tools missing, one-tap install button</li>
|
||||||
|
<li><strong>Bootstrap + Tools in One Tap</strong> — Install button auto-installs bootstrap then build tools</li>
|
||||||
|
<li>Checks tool availability before every build, auto-installs if missing</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="changelog-version">v2.2.0</span>
|
<span class="changelog-version">v2.2.0</span>
|
||||||
<span class="changelog-date">2026-05-19</span>
|
<span class="changelog-date">2026-05-19</span>
|
||||||
|
|||||||
173
www/js/app.js
173
www/js/app.js
@@ -549,6 +549,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((state.currentMode === 'coding' || state.currentMode === 'agentic') && !termState.devToolsInstalled) {
|
||||||
|
checkDevEnvironment();
|
||||||
|
}
|
||||||
|
|
||||||
if (!state.activeConversationId) {
|
if (!state.activeConversationId) {
|
||||||
newConversation();
|
newConversation();
|
||||||
}
|
}
|
||||||
@@ -1901,33 +1905,159 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function ensureBuildTools() {
|
async function ensureBuildTools() {
|
||||||
var check = await shellExec('command -v aapt2 && command -v ecj && (command -v d8 || command -v dx)', termState.homeDir, false);
|
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) return true;
|
if (check.exitCode === 0) {
|
||||||
|
termState.devToolsInstalled = 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');
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
termPrint('[!] Failed to install build tools automatically', 'err');
|
if (Shell) {
|
||||||
termPrint('Please run manually: pkg install aapt2 ecj dx apksigner', 'warning');
|
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;
|
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 = '<span class="dtb-icon">⚠</span> ' +
|
||||||
|
'<span class="dtb-msg">' + msg + '</span>' +
|
||||||
|
'<button class="dtb-install-btn">Install</button>' +
|
||||||
|
'<button class="dtb-dismiss-btn">×</button>';
|
||||||
|
|
||||||
|
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) {
|
async function autoExecuteActions(actions, conv) {
|
||||||
var hasFiles = actions.some(function(a) { return a.type === 'create_file'; });
|
var hasFiles = actions.some(function(a) { return a.type === 'create_file'; });
|
||||||
var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
|
var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
|
||||||
@@ -2585,6 +2715,7 @@
|
|||||||
updateModeSelector();
|
updateModeSelector();
|
||||||
updateHeader();
|
updateHeader();
|
||||||
updateTerminalVisibility();
|
updateTerminalVisibility();
|
||||||
|
checkDevEnvironment();
|
||||||
saveState();
|
saveState();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user