Build a WhatsApp AI Bot on Android
Build a WhatsApp bot powered by Gemma 4 running entirely on your Android phone.
Introduction
30 Second Summary
Your phone is powerful enough to run a real AI model. So why are you still paying monthly API bills to talk to one?
In this project, you will build a WhatsApp AI bot from scratch that runs 100% on your Android phone. The bot uses Gemma 4 as its AI brain, processes everything on-device, and costs absolutely nothing to run.
What You'll Build
A custom WhatsApp AI bot that connects to Gemma 4 running locally via Google AI Edge Gallery, using the Baileys library for WhatsApp integration and a modular skill/command routing system you write yourself.
By the end of this project, you'll have:
- A full Linux (Debian) environment on your Android phone with Termux, PRoot-Distro, and Node.js.
- Gemma 4 E2B running locally with the Edge Server API exposing an OpenAI-compatible endpoint on your phone.
- A working WhatsApp bot with a skill/command routing system and git-based auto-updates for easy improvements.
- Up to 20 Secret Missions to extend your bot with image understanding, voice transcription, web search, scheduled briefings, and more.
Are there any prerequisites?
You need an Android phone running Android 12 or later with at least 6 GB RAM and 8 GB free storage. You also need a WhatsApp account on the same phone. You should be comfortable with terminal commands and basic JavaScript.
Set Up Termux, Debian, and Node.js on Android
Your WhatsApp bot needs a place to run on your phone. Android does not ship with a Linux terminal or a package manager, so you need to create that environment yourself. In this step, you will install Termux (a terminal emulator for Android), set up a full Debian Linux environment inside it, and install Node.js so you can run the JavaScript code that powers your bot.
In This Step, Get Ready To:
- Install Termux from F-Droid and configure Android to keep it running in the background.
- Set up a Debian Linux environment with a dedicated user for your bot.
- Install Node.js 22 and create a networking shim to fix Android-specific errors.
Install Termux and Configure Android Permissions
Termux is a terminal emulator that gives you a Linux-like command line directly on your Android phone. The Google Play Store version of Termux is outdated and broken. You need the F-Droid version, which is actively maintained.
- Download and install Termux from F-Droid. Do NOT use the Google Play Store version.
- Open Termux and run the following command to update all packages.
pkg update && pkg upgrade -y
What does this command do?
pkg update refreshes the list of available packages. pkg upgrade -y installs the latest versions of everything already on your system. The -y flag automatically confirms all prompts so you do not have to press Y for each package.
- Install PRoot-Distro (which lets you run full Linux distributions inside Termux) and git.
pkg install proot-distro git -y
Android aggressively kills background apps to save battery. Your bot needs to stay running, so you need to tell Android to leave Termux alone.
- Open your phone's Settings app.
- Navigate to Apps, then find and tap Termux.
- Tap Battery and set it to Unrestricted.
- Go back to Termux and run the following command to acquire a wake lock.
termux-wake-lock
What is a wake lock?
A wake lock tells Android to keep the CPU running even when the screen is off. Without it, Android would put Termux to sleep and your bot would stop responding to WhatsApp messages.
Check your notification bar. Your Termux notification should now show "wake lock held". Your terminal should display a $ prompt with no errors.
Install Debian and Create a Dedicated User
Termux has its own package ecosystem, but it is not a full Linux distribution. Many tools your bot needs (like build-essential for compiling native modules) work best in a standard Linux environment. PRoot-Distro lets you install and run a full Debian system inside Termux without rooting your phone.
- Install Debian using PRoot-Distro.
proot-distro install debian
- Log in to Debian as root.
proot-distro login debian
You are now inside a Debian Linux environment. Running everything as root is a bad practice because any mistake could affect the entire system. You will create a dedicated user called botuser for your bot.
- Run the following commands to update Debian, install essential packages, and create your bot user.
apt update && apt install -y sudo curl git build-essential ca-certificates
useradd -m -s /bin/bash botuser
echo "botuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
exit
What do these commands do?
- apt update refreshes the Debian package list.
- apt install -y ... installs essential tools. build-essential provides compilers needed by some npm packages. ca-certificates enables HTTPS connections.
- useradd -m -s /bin/bash botuser creates a new user with a home directory and bash as the default shell.
- The sudoers line lets botuser run admin commands without typing a password every time.
- exit logs you out of the root session so you can re-enter as botuser.
- Log back in to Debian as your new bot user. From now on, always use this command to enter Debian.
proot-distro login debian --user botuser
Your terminal prompt should change to botuser@localhost:~$. This confirms you are inside Debian as the botuser.
Install Node.js 22 and Create the Networking Shim
Your bot code is written in JavaScript, so you need Node.js to run it. The Baileys library (which connects to WhatsApp) requires Node.js 20 or higher. You will install Node.js 22 using NVM (Node Version Manager), which makes it easy to install and switch between Node.js versions.
- Make sure you are logged in as botuser inside Debian. Run the following commands to install NVM and then Node.js 22.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install 22
nvm alias default 22
What do these commands do?
The first line downloads and runs the NVM installer script. The next two lines load NVM into your current terminal session so you can use it immediately. nvm install 22 downloads and installs Node.js version 22. nvm alias default 22 makes version 22 the default every time you open a new terminal.
- Verify the installation.
node --version
I see v22.x.x
Node.js is installed correctly. You are ready to move on.
Command not found
NVM may not have loaded into your current session. Try running these two lines again and then retry node --version.
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
Still not working?
If that still does not work, help me troubleshoot NVM installation in Debian on Termux.
There is one more thing to fix. Node.js tries to call os.networkInterfaces() to discover network adapters, but Android blocks this call inside PRoot. This causes an "Error 13" crash. You need to create a small shim file that provides a fake network interface so Node.js does not crash.
- Create the networking shim file and configure Node.js to load it automatically.
cat <<'EOF' > $HOME/net-shim.cjs
const os = require('os');
os.networkInterfaces = () => ({
lo: [{ address: '127.0.0.1', netmask: '255.0.0.0', family: 'IPv4', mac: '00:00:00:00:00:00', internal: true, cidr: '127.0.0.1/8' }]
});
EOF
echo 'export NODE_OPTIONS="--require /home/botuser/net-shim.cjs"' >> ~/.bashrc
source ~/.bashrc
What does this shim do?
The shim replaces Node.js's os.networkInterfaces() function with a version that returns a fake loopback interface. This prevents the Error 13 crash that happens when Node.js tries to access network information that Android blocks inside PRoot.
The NODE_OPTIONS environment variable tells Node.js to load this file before running any script. Adding it to ~/.bashrc makes this automatic every time you open a new terminal session.
- Test that everything works by running a quick Node.js command.
node -e "console.log('Node works!')"
You should see Node works! printed with no errors. If you see any error about network interfaces, the shim did not load correctly.
Still seeing Error 13 or network errors?
Make sure the shim file exists by running cat $HOME/net-shim.cjs. You should see the JavaScript code. Also verify that echo $NODE_OPTIONS prints --require /home/botuser/net-shim.cjs. If either is missing, re-run the commands above. Help me fix the networking shim.
Your Linux foundation is solid. You have Termux running with a wake lock, a full Debian environment with a dedicated user, and Node.js 22 ready to execute JavaScript. Next up, you will disable Android's Phantom Process Killer and get Gemma 4 running locally so your bot has an AI brain to talk to.
Disable the Phantom Process Killer and Install Gemma 4
Your Linux environment is ready. Now you need to solve two problems before writing any bot code. First, Android aggressively kills background processes it considers "phantoms." Your bot would survive for minutes at best without fixing this. Second, you need Gemma 4 running locally with an API your bot can talk to.
In This Step, Get Ready To:
- Disable Android's Phantom Process Killer so your bot stays alive in the background.
- Install and test Gemma 4 E2B locally via Google AI Edge Gallery.
- Enable the Edge Server API so your bot can send HTTP requests to Gemma 4.
Disable the Phantom Process Killer via ADB
Android's Phantom Process Killer is a background service that silently terminates processes it considers unnecessary. Your bot runs as a child process inside Termux, which makes it a prime target. You need to disable this killer using ADB (Android Debug Bridge) from within Termux itself.
First, enable Developer Options on your phone.
- Open Settings on your phone.
- Navigate to About Phone.
- Tap Build number 7 times until you see "You are now a developer."
Now enable the settings you need in Developer Options.
- Go back to Settings and open Developer Options.
- Enable Wireless debugging.
- Enable Disable child process restrictions.
Next, you need to connect to your own phone via ADB from inside Termux. Open a new Termux session by swiping left from the edge of the Termux screen.
- Install ADB in this new Termux session.
pkg install android-tools -y
Now pair Termux with your phone's wireless debugging service.
- Go to Settings, then Developer Options, then Wireless debugging.
- Tap Pair device with pairing code. Note the IP address, port, and pairing code shown on screen.
- In Termux, run the pair command using the IP and port from the pairing dialog.
adb pair [[PAIR_ADDRESS="IP:port from the pairing dialog"]]
What does this command do?
The adb pair command establishes a trusted connection between Termux and your phone's debugging service. You will be prompted to enter the pairing code shown on your screen.
- Enter the pairing code when prompted.
- Now connect to the main wireless debugging port. This is a different port from the pairing port. You can find it on the main Wireless debugging screen.
adb connect [[ADB_ADDRESS="IP:port from the Wireless debugging screen"]]
With ADB connected, you can now disable the Phantom Process Killer.
- Run the following command to disable the killer.
adb shell "settings put global settings_enable_monitor_phantom_procs false"
- Verify the setting was applied.
adb shell "settings get global settings_enable_monitor_phantom_procs"
What should I see?
The command should print false. This means Android will no longer silently kill your background processes. This setting persists across reboots, so you only need to do this once.
Having Trouble Pairing?
The pairing dialog times out quickly. If the code expires, tap Pair device with pairing code again to get a fresh code. Make sure you are using the pairing port (shown in the dialog) for adb pair and the main port (shown on the Wireless debugging screen) for adb connect. These are two different ports. Help me troubleshoot ADB pairing.
Install Gemma 4 via Google AI Edge Gallery
Google AI Edge Gallery is a free app that lets you download and run AI models directly on your phone. You will use it to run Gemma 4 E2B, a 2-billion parameter model optimized for mobile devices. It needs about 3 GB of storage and 3 GB of RAM to run.
- Open the Google Play Store on your phone.
- Search for Google AI Edge Gallery and install it.
- Open the app and navigate to the Model Hub.
- Search for Gemma 4 and download the E2B (Edge Optimized) variant.
Why E2B Instead of E4B?
Gemma 4 comes in two mobile-optimized sizes. E2B has 2 billion effective parameters and uses about 3 GB of RAM. E4B has 4 billion effective parameters and uses about 5 GB of RAM. E2B is the better starting point because it runs comfortably on phones with 6 GB of RAM while leaving room for Termux, Node.js, and WhatsApp to run simultaneously.
The download takes a few minutes depending on your connection. Once it finishes, test that the model works.
- Open the AI Chat tab in the AI Edge Gallery app.
- Send a test message like "What is the capital of France?"
You should see Gemma 4 respond with an answer generated entirely on your phone. No internet connection is needed for inference.
Enable the Edge Server API
Your bot needs a way to send messages to Gemma 4 programmatically. The Edge Server feature exposes Gemma 4 as an OpenAI-compatible HTTP API running on http://127.0.0.1:8888. Your bot will make standard HTTP POST requests to this local endpoint. No special SDK is needed.
- In the AI Edge Gallery app, open the navigation drawer (swipe from the left edge or tap the menu icon).
- Look for Edge Server in the drawer menu.
- Toggle the Edge Server ON.
Don't See the Edge Server Option?
The Edge Server is a community-contributed feature (PR #647 on the google-ai-edge/gallery GitHub repo). It may not be available in the official Play Store version of the app. If you do not see it, check the PR #647 discussion for a community build APK that includes this feature.
Now test the API from your Debian environment to confirm your bot will be able to reach Gemma 4. Switch back to your Termux session running Debian.
- Log into Debian if you are not already inside it.
proot-distro login debian --user botuser
- Test the health endpoint.
curl http://127.0.0.1:8888/health
- Test actual inference by sending a chat completion request.
curl -X POST http://127.0.0.1:8888/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Hello, who are you?"}]}'
What does this command do?
This curl command sends an HTTP POST request to the Edge Server's /v1/chat/completions endpoint. This is the same endpoint format that OpenAI uses. Your bot code will make this exact same type of request to get AI responses from Gemma 4.
What should I see?
The command returns a JSON response containing Gemma 4's answer. It may take a few seconds for the first response as the model loads into memory. This confirms that your Debian environment can reach the Edge Server API and that your bot will be able to talk to Gemma 4 via HTTP.
Getting Connection Refused?
Make sure the Edge Server toggle is ON in the AI Edge Gallery app. The server only runs when the toggle is active. Also confirm that Gemma 4 E2B is the selected model in the app. Help me troubleshoot the Edge Server connection.
You now have two critical pieces in place. Android will not kill your background processes, and Gemma 4 is running as a local API on your phone. Next up, you will write the actual bot code that connects WhatsApp to this AI brain.
Build the WhatsApp Bot Core
Your Android phone now has a full Linux environment with Node.js and a locally running AI model.
The missing piece is the bridge between WhatsApp and Gemma 4. Right now, these two systems have no way to talk to each other. You need a program that listens for incoming WhatsApp messages, forwards them to Gemma 4's local API, and sends the AI's response back to the chat. That program is your bot. In this step, you will write it from scratch using the Baileys library for WhatsApp and plain HTTP requests for Gemma 4.
In This Step, Get Ready To:
- Initialize a Node.js project and install the Baileys WhatsApp library.
- Write a bot that receives WhatsApp messages and sends them to Gemma 4's local API.
- Connect the bot to WhatsApp via QR code and test a live AI conversation.
Initialize the Project and Install Dependencies
Every Node.js project starts with a package.json file that tracks your project's dependencies. You need two packages: @whiskeysockets/baileys (the library that connects to WhatsApp's WebSocket protocol) and @hapi/boom (a utility for handling connection errors cleanly).
- Make sure you are logged into your Debian environment as botuser.
- Run the following commands to create your project directory, initialize it, and install the dependencies.
mkdir ~/gemma-bot && cd ~/gemma-bot
npm init -y
npm install @whiskeysockets/baileys @hapi/boom
What do these commands do?
mkdir ~/gemma-bot creates a new folder for your bot project. npm init -y generates a default package.json file. The npm install command downloads Baileys and its error-handling dependency into your project.
- Create the main bot file.
touch bot.js
- Verify your project structure by running ls.
What should I see?
You should see bot.js, node_modules/, package.json, and package-lock.json in your project directory.
Write the WhatsApp Connection and Message Handler
This is the core of your bot. The code needs to do four things: connect to WhatsApp via a QR code, listen for incoming messages, forward each message to Gemma 4's local API, and send the AI response back to the chat.
- Open bot.js in nano.
nano bot.js
Now write the full bot code. Take your time reading through this. Every section is commented so you understand what each part does.
I'll give it a go!
Write the code for bot.js yourself using these requirements:
- Import makeWASocket and useMultiFileAuthState from @whiskeysockets/baileys.
- Import Boom from @hapi/boom for error handling.
- Use useMultiFileAuthState('auth_info_baileys') to persist your WhatsApp session so you do not need to re-scan the QR code every time.
- Create a socket with printQRInTerminal: true so the QR code displays in your terminal.
- Handle connection.update events for reconnection when the connection drops.
- Handle messages.upsert events to receive incoming messages.
- For each incoming message, extract the text and POST it to http://127.0.0.1:8888/v1/chat/completions using Node.js fetch().
- Send the AI response back using sock.sendMessage(jid, { text: response }).
- Skip messages sent by the bot itself to avoid infinite loops.
Let me check my work
Here is the complete bot.js file with inline comments explaining each section.
// Import the Baileys library for WhatsApp Web protocol
const { default: makeWASocket, useMultiFileAuthState, DisconnectReason } = require('@whiskeysockets/baileys');
const { Boom } = require('@hapi/boom');
// The Gemma 4 Edge Server API endpoint running on your phone
const GEMMA_API = 'http://127.0.0.1:8888/v1/chat/completions';
// Send a message to Gemma 4 and return the AI response
async function askGemma(userMessage) {
// Build the request body in OpenAI-compatible format
const body = {
messages: [
{ role: 'system', content: 'You are a helpful WhatsApp assistant. Keep replies concise.' },
{ role: 'user', content: userMessage }
]
};
// POST to the local Gemma 4 API
const res = await fetch(GEMMA_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
// Parse the JSON response and extract the AI's reply
const data = await res.json();
return data.choices[0].message.content;
}
// Main function that sets up the WhatsApp connection
async function startBot() {
// Load saved session credentials so you only scan QR once
const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys');
// Create the WhatsApp socket connection
const sock = makeWASocket({
auth: state,
printQRInTerminal: true // Displays QR code in your Termux terminal
});
// Save credentials whenever they update (keeps session alive)
sock.ev.on('creds.update', saveCreds);
// Handle connection state changes (disconnect, reconnect)
sock.ev.on('connection.update', (update) => {
const { connection, lastDisconnect } = update;
if (connection === 'close') {
// Check if the disconnect was a logout (do not reconnect)
const statusCode = new Boom(lastDisconnect?.error)?.output?.statusCode;
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
console.log('Connection closed. Reconnecting:', shouldReconnect);
// Restart the bot if it was not a deliberate logout
if (shouldReconnect) {
startBot();
}
} else if (connection === 'open') {
console.log('Bot is connected to WhatsApp!');
}
});
// Listen for incoming messages
sock.ev.on('messages.upsert', async ({ messages }) => {
const msg = messages[0];
// Skip messages sent by the bot itself to prevent infinite loops
if (msg.key.fromMe) return;
// Extract the text content from the message
const text = msg.message?.conversation
|| msg.message?.extendedTextMessage?.text;
// Skip non-text messages (images, stickers, etc.) for now
if (!text) return;
console.log(`Message from ${msg.key.remoteJid}: ${text}`);
try {
// Send the message to Gemma 4 and get the AI response
const reply = await askGemma(text);
// Send the AI response back to the same WhatsApp chat
await sock.sendMessage(msg.key.remoteJid, { text: reply });
} catch (err) {
console.error('Error calling Gemma 4:', err.message);
await sock.sendMessage(msg.key.remoteJid, {
text: 'Sorry, I could not reach the AI right now. Is the Edge Server running?'
});
}
});
}
// Start the bot
startBot();
How does this code work?
The bot has two main parts. The askGemma() function sends a message to Gemma 4's local API using the OpenAI-compatible format. The startBot() function connects to WhatsApp and listens for messages.
When a message arrives, the bot extracts the text, calls askGemma(), and sends the response back to the same chat. The useMultiFileAuthState() function saves your WhatsApp session to the auth_info_baileys/ folder so you only need to scan the QR code once.
Why skip messages from yourself?
The if (msg.key.fromMe) return line is critical. Without it, the bot would see its own replies as new incoming messages. It would then respond to its own response, creating an infinite loop that floods the chat.
- Save the file in nano by pressing Ctrl+O, then Enter, then Ctrl+X to exit.
Test the Bot With a Live WhatsApp Connection
Your bot code is written. Now you need to connect it to WhatsApp and verify that the full message flow works: WhatsApp to Baileys to Gemma 4 and back.
- Make sure the Gemma 4 Edge Server is still running in the AI Edge Gallery app. If you closed it, reopen the app and toggle the Edge Server back on.
- Navigate to your project directory and start the bot.
cd ~/gemma-bot
node bot.js
A large QR code appears in your Termux terminal. This is the WhatsApp pairing QR.
- Open WhatsApp on your phone.
- Go to Settings, then Linked Devices, then Link a Device.
- Scan the QR code displayed in your Termux terminal.
Pro tip: scanning on the same phone
Since both Termux and WhatsApp are on the same phone, you will need to switch between apps. Open the QR in Termux first, then switch to WhatsApp to scan it. The QR code refreshes every 20 seconds, so move quickly. If it expires, the bot will print a new one.
After scanning, your terminal should display a message like "Bot is connected to WhatsApp!" and WhatsApp should show a new linked device in the list.
- Send a message to yourself (or any chat) in WhatsApp. Try something like "What is artificial intelligence?".
The bot receives your message, forwards it to Gemma 4's local API, and sends the AI-generated reply back to the same chat. You should see both your message and the bot's response in WhatsApp.
What should I see?
In your WhatsApp chat, you should see your original message followed by an AI-generated reply from Gemma 4. In the Termux terminal, you should see log output showing the incoming message and the bot processing it. This confirms the full pipeline is working: WhatsApp to Baileys to Gemma 4 and back.
I see the bot's reply
Your bot is alive. Every message you send in WhatsApp is being processed by Gemma 4 running entirely on your phone. No data leaves your device.
I see an error
Seeing "Error calling Gemma 4" in the terminal?
This means the bot cannot reach the Gemma 4 Edge Server. Verify the Edge Server is running in the AI Edge Gallery app. Test it manually with curl http://127.0.0.1:8888/health from your Debian terminal. If that fails, the Edge Server is not active.
QR code keeps refreshing before you can scan it?
The QR code refreshes every 20 seconds. Try using split-screen mode on your phone so you can see both Termux and WhatsApp at the same time. Alternatively, take a screenshot of the QR code quickly and scan from the screenshot.
Something else going wrong?
help me debug my WhatsApp bot connection
- Press Ctrl+C in the terminal to stop the bot once you have confirmed it works.
Will I need to scan the QR code again?
No. Your WhatsApp session is saved in the auth_info_baileys/ folder. The next time you run node bot.js, the bot will reconnect automatically without showing a QR code.
You have a working AI bot that responds to WhatsApp messages using Gemma 4 running entirely on your phone. Next up, you will build a command routing system so the bot can handle different types of requests through specialized skills.
Add a Skill and Command System
Your bot can talk to Gemma 4 and reply in WhatsApp. That is powerful, but every message goes through the same generic conversation path. What if you want the bot to summarize text differently than it answers a trivia question? Or list its own capabilities on demand?
A command router solves this. It inspects every incoming message, checks whether it starts with a command prefix like !, and routes it to a specialized handler called a skill. Messages without a prefix fall back to general Gemma 4 conversation. In this step, you will build that routing system and create your first two skills.
In This Step, Get Ready To:
- Create a command router that detects prefixed messages and dispatches them to skill handlers.
- Build a help skill and a summarize skill as your first two modular commands.
- Wire the router into your bot and test all three message paths: help, summarize, and general conversation.
Create the Skills Directory and Router
The router is the decision-making layer between incoming messages and your bot's response logic. It checks whether a message starts with the ! prefix, extracts the command name and arguments, and looks for a matching skill file in the skills/ directory. If no command matches, the message goes to Gemma 4 for general conversation.
- Create the skills directory inside your project.
mkdir ~/gemma-bot/skills
- Create the router file.
nano ~/gemma-bot/router.js
- Paste the following router code into router.js.
const fs = require('fs');
const path = require('path');
// Load all skill files from the skills/ directory
const skills = new Map();
const skillsDir = path.join(__dirname, 'skills');
function loadSkills() {
skills.clear();
const files = fs.readdirSync(skillsDir).filter(f => f.endsWith('.js'));
for (const file of files) {
const skill = require(path.join(skillsDir, file));
skills.set(skill.name, skill);
console.log(`Loaded skill: ${skill.name}`);
}
}
// Parse a message to extract the command and arguments
function parseCommand(text) {
if (!text.startsWith('!')) return null;
const parts = text.slice(1).split(' ');
const command = parts[0].toLowerCase();
const args = parts.slice(1).join(' ');
return { command, args };
}
// Route a message to the correct skill or fall back to Gemma 4
async function route(sock, message, text, gemmaFallback) {
const parsed = parseCommand(text);
// No command prefix found, use general conversation
if (!parsed) {
return await gemmaFallback(sock, message, text);
}
// Look up the skill by command name
const skill = skills.get(parsed.command);
if (!skill) {
const jid = message.key.remoteJid;
await sock.sendMessage(jid, {
text: `Unknown command: !${parsed.command}\nType !help to see available commands.`
});
return;
}
// Execute the matched skill
await skill.execute(sock, message, parsed.args);
}
// Export the router functions and the skills map
module.exports = { loadSkills, route, skills };
What Does This Code Do?
The router does three things. First, loadSkills() reads every .js file in the skills/ directory and stores each skill in a Map keyed by its name. Second, parseCommand() checks if a message starts with ! and splits it into a command name and arguments. Third, route() ties it all together by dispatching to the right skill or falling back to Gemma 4.
Why a Plugin Architecture Instead of If-Else Chains?
You could handle commands with a long if/else block in bot.js. The skill-file approach is better because adding a new command means dropping a single file into skills/. No changes to the router or bot code needed. This is the same pattern that frameworks like OpenClaw use under the hood.
- Save and exit nano by pressing Ctrl+X, then Y, then Enter.
Build Your First Two Skills
Every skill file exports the same three things: a name (the command word), a description (shown in the help list), and an execute(sock, message, args) function that handles the command. This consistent interface is what lets the router load and dispatch skills automatically.
- Create the help skill.
nano ~/gemma-bot/skills/help.js
- Paste the following code into help.js.
// The help skill reads all loaded skills and lists them
const { skills } = require('../router');
module.exports = {
name: 'help',
description: 'Show all available commands',
async execute(sock, message) {
const jid = message.key.remoteJid;
// Build a formatted list of every loaded skill
let helpText = '*Available Commands:*\n\n';
for (const [name, skill] of skills) {
helpText += `!${name} - ${skill.description}\n`;
}
helpText += '\nSend any message without ! to chat with Gemma 4.';
await sock.sendMessage(jid, { text: helpText });
}
};
What Does This Skill Do?
The help skill imports the skills Map from the router and iterates over every loaded skill. It builds a formatted text list with each command name and description, then sends it back to the WhatsApp chat. This list updates automatically whenever you add new skill files.
- Save and exit, then create the summarize skill.
nano ~/gemma-bot/skills/summarize.js
- Paste the following code into summarize.js.
// The summarize skill sends text to Gemma 4 with a summarization prompt
module.exports = {
name: 'summarize',
description: 'Summarize a block of text using Gemma 4',
async execute(sock, message, args) {
const jid = message.key.remoteJid;
// Check that the user provided text to summarize
if (!args || args.trim().length === 0) {
await sock.sendMessage(jid, {
text: 'Please provide text to summarize.\nExample: !summarize <paste your text here>'
});
return;
}
// Send a typing indicator so the user knows the bot is working
await sock.sendMessage(jid, { text: 'Summarizing...' });
try {
// Call the local Gemma 4 API with a summarization system prompt
const response = await fetch('http://127.0.0.1:8888/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [
{
role: 'system',
content: 'You are a summarization assistant. Provide a clear, concise summary of the following text in 2-3 sentences.'
},
{
role: 'user',
content: args
}
]
})
});
const data = await response.json();
const summary = data.choices[0].message.content;
await sock.sendMessage(jid, { text: `*Summary:*\n\n${summary}` });
} catch (err) {
console.error('Summarize error:', err);
await sock.sendMessage(jid, {
text: 'Could not generate summary. Make sure the AI Edge Gallery Edge Server is running.'
});
}
}
};
What Does This Skill Do?
The summarize skill takes the user's text (everything after !summarize) and sends it to the local Gemma 4 API at http://127.0.0.1:8888. The key difference from general conversation is the system prompt. It tells Gemma 4 to act as a summarization assistant and keep the output to 2-3 sentences. This is the core pattern for every skill you build: wrap a specialized prompt around the same Gemma 4 API call.
- Save and exit nano.
Your project directory should now look like this:
~/gemma-bot/
├── bot.js # main entry point
├── router.js # command routing logic
├── package.json
├── skills/
│ ├── help.js # lists all commands
│ └── summarize.js # summarizes text via Gemma 4
├── auth_info_baileys/ # WhatsApp session (auto-created)
└── node_modules/
Wire the Router Into the Bot and Test
The router and skills are ready, but bot.js still sends every message directly to Gemma 4. You need to update the message handler to go through the router instead.
- Open bot.js in nano.
nano ~/gemma-bot/bot.js
- Add the router import near the top of the file, alongside your existing imports.
const { loadSkills, route } = require('./router');
- Call loadSkills() once at startup, before the socket connection is created. This loads all skill files from the skills/ directory into memory.
// Load all skills before connecting
loadSkills();
- Replace the section inside your messages.upsert handler where you send the message text directly to Gemma 4. Instead of calling the Gemma 4 API directly, pass the message through the router. Move your existing Gemma 4 call into a fallback function that the router calls when no command matches.
// Gemma 4 fallback for general conversation (no command prefix)
async function gemmaFallback(sock, message, text) {
const jid = message.key.remoteJid;
try {
const response = await fetch('http://127.0.0.1:8888/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [{ role: 'user', content: text }]
})
});
const data = await response.json();
const reply = data.choices[0].message.content;
await sock.sendMessage(jid, { text: reply });
} catch (err) {
console.error('Gemma 4 error:', err);
await sock.sendMessage(jid, {
text: 'Could not reach Gemma 4. Is the Edge Server running?'
});
}
}
// Inside your messages.upsert handler, replace the direct Gemma 4 call with:
await route(sock, msg, messageText, gemmaFallback);
What Changed?
Previously, every message went straight to Gemma 4. Now the route() function intercepts every message first. If the message starts with !, the router dispatches it to the matching skill. If there is no prefix, the router calls gemmaFallback(), which is your original Gemma 4 conversation code wrapped in a function.
- Save and exit nano.
- Make sure the AI Edge Gallery Edge Server is running on your phone.
- Start the bot.
cd ~/gemma-bot && node bot.js
You should see the loaded skills listed in the terminal output (e.g., "Loaded skill: help" and "Loaded skill: summarize"). If your WhatsApp session from the previous step is still active, the bot connects automatically without a new QR scan.
Now test all three message paths in WhatsApp.
- Send !help in any WhatsApp chat. You should see a formatted list of available commands including help and summarize.
- Send !summarize Artificial intelligence is a broad field of computer science concerned with building smart machines capable of performing tasks that typically require human intelligence. You should get a concise 2-3 sentence summary back.
- Send a regular message like Tell me a joke without any prefix. The bot should respond with Gemma 4's general conversation, not a command error.
Bot Not Responding to Commands?
If !help returns "Unknown command" or the bot does not respond at all, check the terminal output for errors. Common issues include a typo in the skill file's name field, a missing module.exports in the skill file, or the loadSkills() call not being placed before the socket connection. If the summarize skill returns an error, confirm the Edge Server is running by testing curl http://127.0.0.1:8888/health in a separate Termux session. Help me debug my bot's command routing.
Your bot now has a brain and a skill system. Any time you want a new capability, you drop a single file into skills/ and restart. Next up, you will set up git-based auto-updates so you can push improvements from any computer and pull them to your phone with a single WhatsApp message.
Add Auto-Update and Launch
Your bot has a working skill system and can route commands to specialized handlers. The last piece is making your bot easy to maintain and restart. In this step, you'll set up git-based version control so you can push code updates from a computer and pull them to your phone. You'll also create startup scripts that get the bot running in seconds.
In This Step, Get Ready To:
- Initialize a git repository and optionally push to GitHub for remote editing.
- Build an auto-update skill that pulls the latest code when you message the bot.
- Create startup scripts and run a full end-to-end test of the entire system.
Initialize Git and Push to a Remote
Version control lets you track every change you make to your bot. It also opens the door to editing code on a computer and syncing it to your phone with a single command.
- Navigate to your project directory and initialize a git repository.
cd ~/gemma-bot
git init
git add .
git commit -m "initial bot"
What does this command do?
This sequence initializes a new git repository in your project folder, stages all your files, and creates your first commit. Every file you've built so far is now tracked.
Before pushing to a remote, you should create a .gitignore file to keep sensitive session data out of your repository.
- Create a .gitignore file to exclude your WhatsApp session and dependencies.
cat <<'EOF' > .gitignore
node_modules/
auth_info_baileys/
EOF
git add .gitignore
git commit -m "add gitignore"
Why exclude auth_info_baileys/?
The auth_info_baileys/ folder contains your WhatsApp session credentials. Pushing this to a remote repository would expose your session to anyone with access to the repo.
I have a GitHub account
- Create a new repository on GitHub (private is recommended since this is a personal bot).
- Add the remote and push your code.
git remote add origin [[YOUR_REPO_URL="https://github.com/YOUR_USERNAME/gemma-bot.git"]]
git branch -M main
git push -u origin main
Now you can edit your bot code on any computer, push changes, and pull them to your phone.
I don't have GitHub
No problem. Your local git history still gives you version control and the ability to roll back changes. Skip the push step and continue to the next substep.
Create an Update Script and Skill
Imagine pushing a bug fix from your laptop and updating your bot without ever touching the terminal on your phone. An update skill lets you trigger a git pull directly from WhatsApp.
- Create an update.sh script in your project root.
cat <<'EOF' > ~/gemma-bot/update.sh
#!/bin/bash
cd ~/gemma-bot
# Pull the latest code from the remote repository
git pull origin main
# Install any new dependencies that were added
npm install
echo "Update complete!"
EOF
chmod +x ~/gemma-bot/update.sh
What does this script do?
The script runs two commands. First, git pull origin main downloads the latest code from your remote repository. Then npm install ensures any new dependencies are installed. The chmod +x command makes the script executable.
Now create a skill that triggers this update script from WhatsApp.
- Create skills/update.js with the following code.
const { exec } = require('child_process');
module.exports = {
name: 'update',
description: 'Pull latest code from git and install dependencies',
execute(sock, message, args) {
const jid = message.key.remoteJid;
// Let the user know the update is starting
sock.sendMessage(jid, { text: 'Pulling latest changes...' });
// Run the update script as a child process
exec('bash ~/gemma-bot/update.sh', (error, stdout, stderr) => {
if (error) {
sock.sendMessage(jid, { text: `Update failed: ${error.message}` });
return;
}
// Send the script output back to WhatsApp
const output = stdout || 'No output';
sock.sendMessage(jid, { text: `Update result:\n${output}` });
});
}
};
What does this skill do?
When you send !update in WhatsApp, the router matches this skill and calls its execute function. The skill runs update.sh as a child process and sends the output back to you in the chat. You get confirmation of what changed without opening the terminal.
Pro tip: restart after update
This update skill pulls new code but does not restart the bot process. If your update changes bot.js or router.js, you'll need to manually restart with Ctrl+C and then run node bot.js again. Skill file changes are picked up automatically if your router reloads skills dynamically.
Create a Startup Script and Test the Full System
Every time you restart your phone or reopen Termux, you need to log into Debian and start the bot. A startup script and a shell alias reduce this to a single command.
- Create a start.sh script in your project root.
cat <<'EOF' > ~/gemma-bot/start.sh
#!/bin/bash
# Ensure the wake lock is active to prevent Android from sleeping
termux-wake-lock
# Navigate to the project and start the bot
cd ~/gemma-bot
node bot.js
EOF
chmod +x ~/gemma-bot/start.sh
- Add a convenience alias to your .bashrc so you can start the bot with a single command.
echo 'alias start-bot="cd ~/gemma-bot && node bot.js"' >> ~/.bashrc
source ~/.bashrc
What is a shell alias?
A shell alias is a shortcut that maps a short command to a longer one. After adding this alias, typing start-bot in your terminal runs cd ~/gemma-bot && node bot.js automatically.
Your project directory should now look like this:
~/gemma-bot/
├── bot.js # Main entry point
├── router.js # Command routing logic
├── package.json
├── start.sh # Startup script
├── update.sh # Auto-update script
├── .gitignore # Excludes session data and node_modules
├── skills/
│ ├── help.js # Lists all commands
│ ├── summarize.js # Summarizes text via Gemma 4
│ └── update.js # Pulls latest code from git
├── auth_info_baileys/ # WhatsApp session (auto-created)
└── node_modules/ # Dependencies
Time for the full end-to-end test. This confirms every piece of your system works together.
- Close and reopen Termux to simulate a fresh start.
- Log into your Debian environment.
proot-distro login debian --user botuser
- Make sure the Google AI Edge Gallery app is open with Gemma 4 loaded and the Edge Server toggled ON.
- Start the bot using your new alias.
start-bot
Your bot should connect to WhatsApp without needing to scan a QR code again. The session you created earlier is saved in the auth_info_baileys/ folder.
Now run through the full test sequence in WhatsApp:
- Send !help and confirm the skill list appears with all your commands.
- Send a general message like What is the meaning of life? and confirm Gemma 4 responds with a conversational answer.
- Send !summarize Artificial intelligence is a broad field of computer science concerned with building smart machines capable of performing tasks that typically require human intelligence. and confirm you get a concise summary back.
- Send !update and confirm the update check runs and reports back.
Bot not responding to messages?
First, check that the Edge Server is running by opening the AI Edge Gallery app and confirming the Edge Server toggle is ON. Then verify the API is reachable by running curl http://127.0.0.1:8888/health in a separate Termux session.
If the bot connected but messages are not being processed, check the terminal for error output. Help me debug my WhatsApp bot.
All four test messages got proper responses. Your bot is fully operational. It connects to WhatsApp, routes commands through your skill system, falls back to Gemma 4 for general conversation, and can update itself from git. Everything runs on your phone with zero cloud costs.
Secret mission
Extend Your Bot with Advanced Capabilities
Your bot can do much more than answer questions. These 20 secret missions add real-world capabilities like image understanding, voice transcription, web search, multi-platform support, and interactive rich messages. Each mission builds on the skill system you already created.
Before you go
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on your Android phone with free, open-source tools, so there are no ongoing cloud costs.
Resources you used:
- Termux app with a Debian environment installed via PRoot-Distro (free, stored locally).
- Gemma 4 E2B model files in Google AI Edge Gallery (free, about 3 GB of local storage).
- Your bot code in ~/gemma-bot including Node.js dependencies (free, local).
- WhatsApp linked device session stored in auth_info_baileys/ (free, local).
- Android Developer Options and Wireless Debugging settings (free, system-level).
Keep everything running
No action needed. Choose this if you are still actively building and want the bot to keep responding to WhatsApp messages.
- Leave Termux running with termux-wake-lock active. The bot will continue responding to messages.
- Keep the Google AI Edge Gallery app open or in the background so the Edge Server stays available.
- Android may occasionally kill Termux overnight on some devices, even with all background protections enabled. If this happens, reopen Termux and run start-bot to restart.
Pause - I'll come back to this later
Stop the bot to free up memory, but keep all your files and configuration intact. You can restart anytime.
- Press Ctrl+C in Termux to stop the bot process.
- Close the Google AI Edge Gallery app to free up the RAM used by Gemma 4.
- Your WhatsApp linked device session persists in the auth_info_baileys/ folder. You will not need to scan the QR code again.
- To restart later, open Termux, run proot-distro login debian --user botuser, then run start-bot. Make sure to reopen the AI Edge Gallery and enable the Edge Server first.
Delete - I don't want to use this again
Remove all project resources and start fresh. This frees up about 3-6 GB of storage depending on which Gemma 4 models you downloaded.
- Unlink the bot from WhatsApp: open WhatsApp, go to Settings > Linked Devices, tap your linked device, and select Log Out.
- Remove the Debian environment from Termux: run proot-distro remove debian in the Termux shell. This deletes all bot code, Node.js, and the botuser account.
- Uninstall the Google AI Edge Gallery app from your phone to free up the Gemma 4 model storage.
- Uninstall Termux from your phone if you no longer need it.
- Optionally, re-enable the Phantom Process Killer by running adb shell "settings put global settings_enable_monitor_phantom_procs true" before uninstalling Termux. You can also disable Developer Options in your phone settings.
Mission accomplished
That's a Wrap!
You just built a private AI assistant from scratch that runs entirely on your Android phone. No cloud APIs. No monthly bills. No data leaving your device. Every message flows through your own code, hits a local AI model, and returns a response without ever touching the internet.
Here's what you learned:
- Set up a full Linux environment on Android using Termux, PRoot-Distro, and Debian, including disabling Android's Phantom Process Killer to keep background processes alive.
- Built a WhatsApp bot from scratch using the Baileys library and connected it to Gemma 4 running locally via Google AI Edge Gallery's Edge Server API.
- Created a skill and command routing system that makes your bot extensible, with modular skill handlers and a fallback to general AI conversation.
- Extended your bot with advanced capabilities like image understanding, scheduled briefings, voice transcription, web search, multi-model routing, admin controls, and interactive rich messages.
Ready to quiz yourself?