openclaw-droid 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/bin/openclaw +8 -0
- package/install.sh +67 -0
- package/lib/bionic-bypass.js +64 -0
- package/lib/index.js +304 -0
- package/lib/installer.js +282 -0
- package/lib/postinstall.js +36 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mithun Gowda B
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# ClawDroid š¤
|
|
2
|
+
|
|
3
|
+
> **Run OpenClaw AI Gateway on Android via Termux**
|
|
4
|
+
> One-command setup. Optimized for mobile. Bionic Bypass included.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
**ClawDroid** makes running [OpenClaw](https://github.com/openclaw/openclaw) on Android effortless. It handles the environment setup (proot-distro, Ubuntu, Node.js) and fixes Android-specific issues automatically.
|
|
11
|
+
|
|
12
|
+
## š Why ClawDroid?
|
|
13
|
+
|
|
14
|
+
Running standard Node.js AI tools on Android is painful because of:
|
|
15
|
+
* **Bionic libc**: Android's C library differs from Linux (glibc), breaking `os.networkInterfaces()` and DNS lookups.
|
|
16
|
+
* **Permissions**: Termux has restricted access to system resources.
|
|
17
|
+
* **Environment**: Many tools expect a full Linux userland (Ubuntu/Debian).
|
|
18
|
+
|
|
19
|
+
**ClawDroid solves this by:**
|
|
20
|
+
1. Creating a lightweight **Ubuntu** container inside Termux.
|
|
21
|
+
2. Injecting a **Bionic Bypass** script to fix networking.
|
|
22
|
+
3. Providing a simple CLI (`clawdroid`) to manage the gateway.
|
|
23
|
+
|
|
24
|
+
## š¦ Installation
|
|
25
|
+
|
|
26
|
+
### Prerequisites
|
|
27
|
+
* **Android 10+**
|
|
28
|
+
* **Termux** (Install from [F-Droid](https://f-droid.org/packages/com.termux/), NOT Play Store)
|
|
29
|
+
* ~2GB free storage
|
|
30
|
+
|
|
31
|
+
### One-Command Setup
|
|
32
|
+
Open Termux and run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl -fsSL https://raw.githubusercontent.com/NOSYTLABS/clawdroid/main/install.sh | bash
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or via npm:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g clawdroid
|
|
42
|
+
clawdroid setup
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## š® Usage
|
|
46
|
+
|
|
47
|
+
### 1. Initialize
|
|
48
|
+
First, configure your API keys:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
clawdroid onboarding
|
|
52
|
+
```
|
|
53
|
+
> **IMPORTANT:** Select **Loopback (127.0.0.1)** for Binding.
|
|
54
|
+
|
|
55
|
+
### 2. Start Gateway
|
|
56
|
+
Launch the OpenClaw gateway:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
clawdroid start
|
|
60
|
+
```
|
|
61
|
+
The dashboard will be available at: **http://127.0.0.1:18789**
|
|
62
|
+
|
|
63
|
+
### 3. Other Commands
|
|
64
|
+
|
|
65
|
+
| Command | Description |
|
|
66
|
+
| :--- | :--- |
|
|
67
|
+
| `clawdroid status` | Check installation health |
|
|
68
|
+
| `clawdroid update` | Update OpenClaw to the latest version |
|
|
69
|
+
| `clawdroid shell` | Open the Ubuntu shell |
|
|
70
|
+
| `clawdroid repair` | Re-install dependencies if broken |
|
|
71
|
+
| `clawdroid <cmd>` | Run any OpenClaw command (e.g., `clawdroid doctor`) |
|
|
72
|
+
|
|
73
|
+
## š§© Architecture
|
|
74
|
+
|
|
75
|
+
```mermaid
|
|
76
|
+
graph TD
|
|
77
|
+
A[User] -->|clawdroid start| B(Termux)
|
|
78
|
+
B -->|proot-distro| C{Ubuntu Container}
|
|
79
|
+
C -->|Bionic Bypass| D[OpenClaw Gateway]
|
|
80
|
+
D -->|HTTP| E[Web Dashboard]
|
|
81
|
+
D -->|API| F[LLM Providers]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## ā ļø Troubleshooting
|
|
85
|
+
|
|
86
|
+
**"Setup not complete" error**
|
|
87
|
+
* Run `clawdroid setup` again.
|
|
88
|
+
* If it persists, run `clawdroid repair`.
|
|
89
|
+
|
|
90
|
+
**Process killed in background**
|
|
91
|
+
* Go to Android Settings ā Apps ā Termux ā Battery ā **Unrestricted**.
|
|
92
|
+
|
|
93
|
+
## š License
|
|
94
|
+
|
|
95
|
+
MIT License.
|
package/bin/openclaw
ADDED
package/install.sh
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# OpenClaw Droid Installer
|
|
4
|
+
# One-liner: curl -fsSL https://raw.githubusercontent.com/NosytLabs/openclaw-droid/main/install.sh | bash
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
# Colors
|
|
10
|
+
RED='\033[0;31m'
|
|
11
|
+
GREEN='\033[0;32m'
|
|
12
|
+
YELLOW='\033[1;33m'
|
|
13
|
+
BLUE='\033[0;34m'
|
|
14
|
+
NC='\033[0m'
|
|
15
|
+
|
|
16
|
+
echo -e "${BLUE}"
|
|
17
|
+
echo "-------------------------------------------"
|
|
18
|
+
echo " OpenClaw Droid Installer v1.0.0"
|
|
19
|
+
echo "-------------------------------------------"
|
|
20
|
+
echo -e "${NC}"
|
|
21
|
+
|
|
22
|
+
# Check if running in Termux
|
|
23
|
+
if [ ! -d "/data/data/com.termux" ] && [ -z "$TERMUX_VERSION" ]; then
|
|
24
|
+
echo -e "${YELLOW}Warning:${NC} Not running in Termux - some features may not work"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Update and install packages
|
|
28
|
+
echo -e "\n${BLUE}[1/3]${NC} Installing required packages..."
|
|
29
|
+
|
|
30
|
+
# Update Termux repositories
|
|
31
|
+
echo -e " ${BLUE}ā¢${NC} Updating Termux repositories..."
|
|
32
|
+
pkg update -y || true
|
|
33
|
+
pkg upgrade -y || true
|
|
34
|
+
|
|
35
|
+
echo -e " ${BLUE}ā¢${NC} Installing dependencies..."
|
|
36
|
+
pkg install -y nodejs-lts git proot-distro android-tools termux-api
|
|
37
|
+
|
|
38
|
+
echo -e " ${GREEN}ā${NC} Node.js $(node --version)"
|
|
39
|
+
echo -e " ${GREEN}ā${NC} npm $(npm --version)"
|
|
40
|
+
echo -e " ${GREEN}ā${NC} git installed"
|
|
41
|
+
echo -e " ${GREEN}ā${NC} proot-distro installed"
|
|
42
|
+
echo -e " ${GREEN}ā${NC} adb $(adb version | head -n 1)"
|
|
43
|
+
echo -e " ${GREEN}ā${NC} termux-api installed"
|
|
44
|
+
|
|
45
|
+
# Install openclaw-droid from npm
|
|
46
|
+
echo -e "\n${BLUE}[2/3]${NC} Installing OpenClaw Droid..."
|
|
47
|
+
npm install -g openclaw-droid
|
|
48
|
+
|
|
49
|
+
echo -e "\n${BLUE}[3/3]${NC} Verifying Android tools..."
|
|
50
|
+
adb start-server >/dev/null 2>&1 || true
|
|
51
|
+
adb devices || true
|
|
52
|
+
|
|
53
|
+
echo -e "\n${GREEN}āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā${NC}"
|
|
54
|
+
echo -e "${GREEN}Installation complete!${NC}"
|
|
55
|
+
echo -e "${GREEN}āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā${NC}"
|
|
56
|
+
echo ""
|
|
57
|
+
echo -e "${YELLOW}Next steps:${NC}"
|
|
58
|
+
echo " 1. Run setup: openclaw setup"
|
|
59
|
+
echo " 2. Run onboarding: openclaw onboarding"
|
|
60
|
+
echo " ā Select 'Loopback (127.0.0.1)' when asked!"
|
|
61
|
+
echo " 3. Start gateway: openclaw start"
|
|
62
|
+
echo ""
|
|
63
|
+
echo -e "Dashboard: ${BLUE}http://127.0.0.1:18789${NC}"
|
|
64
|
+
echo ""
|
|
65
|
+
echo -e "${YELLOW}Tip:${NC} Disable battery optimization for Termux in Android settings"
|
|
66
|
+
echo -e "${YELLOW}Tip:${NC} Install Termux:API app from F-Droid for camera, wakelock, and sensors"
|
|
67
|
+
echo ""
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const BYPASS_SCRIPT = `
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const originalNetworkInterfaces = os.networkInterfaces;
|
|
7
|
+
|
|
8
|
+
os.networkInterfaces = function() {
|
|
9
|
+
try {
|
|
10
|
+
const interfaces = originalNetworkInterfaces.call(os);
|
|
11
|
+
if (interfaces && Object.keys(interfaces).length > 0) {
|
|
12
|
+
return interfaces;
|
|
13
|
+
}
|
|
14
|
+
} catch (e) {}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
lo: [
|
|
18
|
+
{
|
|
19
|
+
address: '127.0.0.1',
|
|
20
|
+
netmask: '255.0.0.0',
|
|
21
|
+
family: 'IPv4',
|
|
22
|
+
mac: '00:00:00:00:00:00',
|
|
23
|
+
internal: true,
|
|
24
|
+
cidr: '127.0.0.1/8'
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export function getBypassScriptPath() {
|
|
32
|
+
const homeDir = process.env.HOME || '/data/data/com.termux/files/home';
|
|
33
|
+
return path.join(homeDir, '.clawdroid', 'bionic-bypass.js');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function installBypass() {
|
|
37
|
+
const scriptPath = getBypassScriptPath();
|
|
38
|
+
const scriptDir = path.dirname(scriptPath);
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(scriptDir)) {
|
|
41
|
+
fs.mkdirSync(scriptDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fs.writeFileSync(scriptPath, BYPASS_SCRIPT, 'utf8');
|
|
45
|
+
fs.chmodSync(scriptPath, '644');
|
|
46
|
+
|
|
47
|
+
return scriptPath;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getNodeOptions() {
|
|
51
|
+
const scriptPath = getBypassScriptPath();
|
|
52
|
+
return `--require "${scriptPath}"`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isAndroid() {
|
|
56
|
+
return process.platform === 'android' ||
|
|
57
|
+
fs.existsSync('/data/data/com.termux') ||
|
|
58
|
+
process.env.TERMUX_VERSION !== undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function checkBypassInstalled() {
|
|
62
|
+
const scriptPath = getBypassScriptPath();
|
|
63
|
+
return fs.existsSync(scriptPath);
|
|
64
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configureTermux,
|
|
3
|
+
getInstallStatus,
|
|
4
|
+
installProot,
|
|
5
|
+
installUbuntu,
|
|
6
|
+
setupProotUbuntu,
|
|
7
|
+
setupBionicBypassInProot,
|
|
8
|
+
runInProot,
|
|
9
|
+
runInProotWithCallback
|
|
10
|
+
} from './installer.js';
|
|
11
|
+
import { isAndroid } from './bionic-bypass.js';
|
|
12
|
+
import { spawn } from 'child_process';
|
|
13
|
+
|
|
14
|
+
const VERSION = '1.0.0';
|
|
15
|
+
|
|
16
|
+
function printBanner() {
|
|
17
|
+
console.log(`
|
|
18
|
+
|
|
19
|
+
OPENCLAW-DROID v${VERSION}
|
|
20
|
+
|
|
21
|
+
`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function printHelp() {
|
|
25
|
+
console.log(`
|
|
26
|
+
Usage: openclaw [command] [args...]
|
|
27
|
+
|
|
28
|
+
If run without arguments, it starts the OpenClaw gateway.
|
|
29
|
+
|
|
30
|
+
Commands:
|
|
31
|
+
start Start OpenClaw gateway (Default)
|
|
32
|
+
setup Full installation (proot + Ubuntu + OpenClaw)
|
|
33
|
+
update Update OpenClaw to the latest version
|
|
34
|
+
repair Re-install OpenClaw and dependencies
|
|
35
|
+
status Check installation status
|
|
36
|
+
shell Open Ubuntu shell with OpenClaw ready
|
|
37
|
+
help Show this help message
|
|
38
|
+
openclaw onboarding
|
|
39
|
+
openclaw gateway -v
|
|
40
|
+
openclaw doctor
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
openclaw # Start gateway
|
|
44
|
+
openclaw setup # First-time setup
|
|
45
|
+
openclaw update # Update OpenClaw
|
|
46
|
+
openclaw repair # Fix installation
|
|
47
|
+
openclaw shell # Enter Ubuntu shell
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function runSetup() {
|
|
52
|
+
console.log('Starting OpenClaw Droid setup for Termux...\n');
|
|
53
|
+
|
|
54
|
+
if (!isAndroid()) {
|
|
55
|
+
console.log('Warning: This package is designed for Android/Termux.');
|
|
56
|
+
console.log('Some features may not work on other platforms.\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let status = getInstallStatus();
|
|
60
|
+
|
|
61
|
+
console.log('[1/5] Checking proot-distro...');
|
|
62
|
+
if (!status.proot) {
|
|
63
|
+
installProot();
|
|
64
|
+
} else {
|
|
65
|
+
console.log(' ā proot-distro installed');
|
|
66
|
+
}
|
|
67
|
+
console.log('');
|
|
68
|
+
|
|
69
|
+
console.log('[2/5] Checking Ubuntu in proot...');
|
|
70
|
+
status = getInstallStatus();
|
|
71
|
+
if (!status.ubuntu) {
|
|
72
|
+
installUbuntu();
|
|
73
|
+
} else {
|
|
74
|
+
console.log(' ā Ubuntu installed');
|
|
75
|
+
}
|
|
76
|
+
console.log('');
|
|
77
|
+
|
|
78
|
+
console.log('[3/5] Setting up Node.js and OpenClaw in Ubuntu...');
|
|
79
|
+
status = getInstallStatus();
|
|
80
|
+
if (!status.openClawInProot) {
|
|
81
|
+
setupProotUbuntu();
|
|
82
|
+
} else {
|
|
83
|
+
console.log(' ā OpenClaw already installed in proot');
|
|
84
|
+
}
|
|
85
|
+
console.log('');
|
|
86
|
+
|
|
87
|
+
console.log('[4/5] Setting up Bionic Bypass in proot...');
|
|
88
|
+
setupBionicBypassInProot();
|
|
89
|
+
console.log('');
|
|
90
|
+
|
|
91
|
+
console.log('[5/5] Configuring Termux...');
|
|
92
|
+
configureTermux();
|
|
93
|
+
console.log('');
|
|
94
|
+
|
|
95
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
96
|
+
console.log('Setup complete!');
|
|
97
|
+
console.log('');
|
|
98
|
+
console.log('Next steps:');
|
|
99
|
+
console.log(' 1. Run onboarding: openclaw onboarding');
|
|
100
|
+
console.log(' ā Select "Loopback (127.0.0.1)" when asked!');
|
|
101
|
+
console.log(' 2. Start gateway: openclaw start');
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log('Dashboard: http://127.0.0.1:18789');
|
|
104
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function showStatus() {
|
|
108
|
+
process.stdout.write('Checking installation status...');
|
|
109
|
+
const status = getInstallStatus();
|
|
110
|
+
process.stdout.write('\r' + ' '.repeat(35) + '\r');
|
|
111
|
+
|
|
112
|
+
console.log('Installation Status:\n');
|
|
113
|
+
console.log('Termux:');
|
|
114
|
+
console.log(` proot-distro: ${status.proot ? 'ā installed' : 'ā missing'}`);
|
|
115
|
+
console.log(` Ubuntu (proot): ${status.ubuntu ? 'ā installed' : 'ā not installed'}`);
|
|
116
|
+
console.log('');
|
|
117
|
+
|
|
118
|
+
if (status.ubuntu) {
|
|
119
|
+
console.log('Inside Ubuntu:');
|
|
120
|
+
console.log(` OpenClaw: ${status.openClawInProot ? 'ā installed' : 'ā not installed'}`);
|
|
121
|
+
console.log(` Bionic Bypass: ${status.bionicBypassInProot ? 'ā configured' : 'ā not configured'}`);
|
|
122
|
+
console.log('');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (status.proot && status.ubuntu && status.openClawInProot) {
|
|
126
|
+
console.log('Status: ā Ready to run!');
|
|
127
|
+
console.log('');
|
|
128
|
+
} else {
|
|
129
|
+
console.log('Status: ā Setup incomplete');
|
|
130
|
+
console.log('Run: clawdroid setup');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function updateOpenClaw() {
|
|
135
|
+
const status = getInstallStatus();
|
|
136
|
+
|
|
137
|
+
if (!status.proot || !status.ubuntu) {
|
|
138
|
+
console.error('proot/Ubuntu not installed. Run: openclaw setup');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log('Updating OpenClaw to the latest version...');
|
|
143
|
+
|
|
144
|
+
const proc = runInProotWithCallback('npm install -g openclaw@latest', () => {
|
|
145
|
+
console.log('npm output:');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
proc.on('close', (code) => {
|
|
149
|
+
if (code === 0) {
|
|
150
|
+
console.log('\nā OpenClaw updated successfully!');
|
|
151
|
+
console.log('Run "openclaw start" to launch the gateway.');
|
|
152
|
+
} else {
|
|
153
|
+
console.error(`\nā Update failed with code ${code}`);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function startGateway() {
|
|
159
|
+
const status = getInstallStatus();
|
|
160
|
+
|
|
161
|
+
if (!status.proot || !status.ubuntu) {
|
|
162
|
+
console.error('proot/Ubuntu not installed. Run: clawdroid setup');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!status.openClawInProot) {
|
|
167
|
+
console.error('OpenClaw not installed in proot. Run: clawdroid setup');
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const frames = ['ā ', 'ā ', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ', 'ā '];
|
|
172
|
+
let i = 0;
|
|
173
|
+
let started = false;
|
|
174
|
+
const DASHBOARD_URL = 'http://127.0.0.1:18789';
|
|
175
|
+
|
|
176
|
+
const spinner = setInterval(() => {
|
|
177
|
+
if (!started) {
|
|
178
|
+
process.stdout.write(`\r${frames[i++ % frames.length]} Starting OpenClaw gateway...`);
|
|
179
|
+
}
|
|
180
|
+
}, 80);
|
|
181
|
+
|
|
182
|
+
const checkDashboard = setInterval(async () => {
|
|
183
|
+
if (started) return;
|
|
184
|
+
try {
|
|
185
|
+
const response = await fetch(DASHBOARD_URL, { method: 'HEAD', signal: AbortSignal.timeout(1000) });
|
|
186
|
+
if (response.ok || response.status < 500) {
|
|
187
|
+
started = true;
|
|
188
|
+
clearInterval(spinner);
|
|
189
|
+
clearInterval(checkDashboard);
|
|
190
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
191
|
+
console.log('ā OpenClaw gateway started!\n');
|
|
192
|
+
console.log(`Dashboard: ${DASHBOARD_URL}`);
|
|
193
|
+
console.log('Press Ctrl+C to stop\n');
|
|
194
|
+
console.log('ā'.repeat(45) + '\n');
|
|
195
|
+
}
|
|
196
|
+
} catch {}
|
|
197
|
+
}, 500);
|
|
198
|
+
|
|
199
|
+
const gateway = runInProot('openclaw gateway --verbose');
|
|
200
|
+
|
|
201
|
+
gateway.on('error', (err) => {
|
|
202
|
+
clearInterval(spinner);
|
|
203
|
+
clearInterval(checkDashboard);
|
|
204
|
+
console.error('\nFailed to start gateway:', err.message);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
gateway.on('close', (code) => {
|
|
208
|
+
clearInterval(spinner);
|
|
209
|
+
clearInterval(checkDashboard);
|
|
210
|
+
if (!started) {
|
|
211
|
+
console.log('\nGateway exited before starting. Run: clawdroid onboarding');
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function runOpenclawCommand(args) {
|
|
217
|
+
const status = getInstallStatus();
|
|
218
|
+
|
|
219
|
+
if (!status.proot || !status.ubuntu || !status.openClawInProot) {
|
|
220
|
+
console.error('Setup not complete. Run: clawdroid setup');
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const command = args.join(' ');
|
|
225
|
+
console.log(`Running: openclaw ${command}\n`);
|
|
226
|
+
|
|
227
|
+
if (args[0] === 'onboarding') {
|
|
228
|
+
console.log('TIP: Select "Loopback (127.0.0.1)" when asked for binding!\n');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const proc = runInProot(`openclaw ${command}`);
|
|
232
|
+
|
|
233
|
+
proc.on('error', (err) => {
|
|
234
|
+
console.error('Failed to run command:', err.message);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function openShell() {
|
|
239
|
+
const status = getInstallStatus();
|
|
240
|
+
|
|
241
|
+
if (!status.proot || !status.ubuntu) {
|
|
242
|
+
console.error('proot/Ubuntu not installed. Run: clawdroid setup');
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.log('Entering Ubuntu shell...');
|
|
247
|
+
console.log('Type "exit" to return to Termux\n');
|
|
248
|
+
|
|
249
|
+
const shell = spawn('proot-distro', ['login', 'ubuntu'], {
|
|
250
|
+
stdio: 'inherit'
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
shell.on('error', (err) => {
|
|
254
|
+
console.error('Failed to open shell:', err.message);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function main(args) {
|
|
259
|
+
const command = args[0] || 'start';
|
|
260
|
+
|
|
261
|
+
printBanner();
|
|
262
|
+
|
|
263
|
+
switch (command) {
|
|
264
|
+
case 'setup':
|
|
265
|
+
case 'install':
|
|
266
|
+
await runSetup();
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case 'status':
|
|
270
|
+
showStatus();
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case 'update':
|
|
274
|
+
updateOpenClaw();
|
|
275
|
+
break;
|
|
276
|
+
|
|
277
|
+
case 'repair':
|
|
278
|
+
console.log('Repairing installation...');
|
|
279
|
+
await setupProotUbuntu();
|
|
280
|
+
setupBionicBypassInProot();
|
|
281
|
+
console.log('Repair complete!');
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
case 'start':
|
|
285
|
+
case 'run':
|
|
286
|
+
startGateway();
|
|
287
|
+
break;
|
|
288
|
+
|
|
289
|
+
case 'shell':
|
|
290
|
+
case 'ubuntu':
|
|
291
|
+
openShell();
|
|
292
|
+
break;
|
|
293
|
+
|
|
294
|
+
case 'help':
|
|
295
|
+
case '--help':
|
|
296
|
+
case '-h':
|
|
297
|
+
printHelp();
|
|
298
|
+
break;
|
|
299
|
+
|
|
300
|
+
default:
|
|
301
|
+
runOpenclawCommand(args);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
package/lib/installer.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { execSync, spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { installBypass, getBypassScriptPath, getNodeOptions } from './bionic-bypass.js';
|
|
5
|
+
|
|
6
|
+
const HOME = process.env.HOME || '/data/data/com.termux/files/home';
|
|
7
|
+
const BASHRC = path.join(HOME, '.bashrc');
|
|
8
|
+
const ZSHRC = path.join(HOME, '.zshrc');
|
|
9
|
+
const PROOT_ROOTFS = '/data/data/com.termux/files/usr/var/lib/proot-distro/installed-rootfs';
|
|
10
|
+
const PROOT_UBUNTU_ROOT = path.join(PROOT_ROOTFS, 'ubuntu', 'root');
|
|
11
|
+
|
|
12
|
+
export function checkDependencies() {
|
|
13
|
+
const deps = {
|
|
14
|
+
node: false,
|
|
15
|
+
npm: false,
|
|
16
|
+
git: false,
|
|
17
|
+
proot: false
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
execSync('node --version', { stdio: 'pipe' });
|
|
22
|
+
deps.node = true;
|
|
23
|
+
} catch {}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
execSync('npm --version', { stdio: 'pipe' });
|
|
27
|
+
deps.npm = true;
|
|
28
|
+
} catch {}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
execSync('git --version', { stdio: 'pipe' });
|
|
32
|
+
deps.git = true;
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
execSync('which proot-distro', { stdio: 'pipe' });
|
|
37
|
+
deps.proot = true;
|
|
38
|
+
} catch {}
|
|
39
|
+
|
|
40
|
+
return deps;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function installTermuxDeps() {
|
|
44
|
+
console.log('Installing Termux dependencies...');
|
|
45
|
+
const packages = ['nodejs-lts', 'git', 'openssh'];
|
|
46
|
+
try {
|
|
47
|
+
execSync('pkg update -y', { stdio: 'inherit' });
|
|
48
|
+
execSync(`pkg install -y ${packages.join(' ')}`, { stdio: 'inherit' });
|
|
49
|
+
return true;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('Failed to install Termux packages:', err.message);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function setupBionicBypass() {
|
|
57
|
+
console.log('Setting up Bionic Bypass...');
|
|
58
|
+
const scriptPath = installBypass();
|
|
59
|
+
const nodeOptions = getNodeOptions();
|
|
60
|
+
const exportLine = `export NODE_OPTIONS="${nodeOptions}"`;
|
|
61
|
+
|
|
62
|
+
for (const rcFile of [BASHRC, ZSHRC]) {
|
|
63
|
+
if (fs.existsSync(rcFile)) {
|
|
64
|
+
const content = fs.readFileSync(rcFile, 'utf8');
|
|
65
|
+
if (!content.includes('bionic-bypass')) {
|
|
66
|
+
fs.appendFileSync(rcFile, `\n# ClawDroid Bionic Bypass\n${exportLine}\n`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
process.env.NODE_OPTIONS = nodeOptions;
|
|
72
|
+
return scriptPath;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function installOpenClaw() {
|
|
76
|
+
console.log('Installing OpenClaw...');
|
|
77
|
+
try {
|
|
78
|
+
execSync('npm install -g openclaw', { stdio: 'inherit' });
|
|
79
|
+
return true;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error('Failed to install OpenClaw:', err.message);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function configureTermux() {
|
|
87
|
+
console.log('Configuring Termux for background operation...');
|
|
88
|
+
const configDir = path.join(HOME, '.clawdroid');
|
|
89
|
+
if (!fs.existsSync(configDir)) {
|
|
90
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const wakeLockScript = path.join(configDir, 'wakelock.sh');
|
|
94
|
+
const wakeLockContent = `#!/bin/bash
|
|
95
|
+
termux-wake-lock
|
|
96
|
+
trap "termux-wake-unlock" EXIT
|
|
97
|
+
exec "$@"
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
fs.writeFileSync(wakeLockScript, wakeLockContent, 'utf8');
|
|
101
|
+
fs.chmodSync(wakeLockScript, '755');
|
|
102
|
+
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getInstallStatus() {
|
|
107
|
+
const PROOT_ROOTFS = '/data/data/com.termux/files/usr/var/lib/proot-distro/installed-rootfs';
|
|
108
|
+
|
|
109
|
+
let hasProot = false;
|
|
110
|
+
try {
|
|
111
|
+
execSync('command -v proot-distro', { stdio: 'pipe' });
|
|
112
|
+
hasProot = true;
|
|
113
|
+
} catch {}
|
|
114
|
+
|
|
115
|
+
let hasUbuntu = false;
|
|
116
|
+
try {
|
|
117
|
+
hasUbuntu = fs.existsSync(path.join(PROOT_ROOTFS, 'ubuntu'));
|
|
118
|
+
} catch {}
|
|
119
|
+
|
|
120
|
+
let hasOpenClawInProot = false;
|
|
121
|
+
if (hasUbuntu) {
|
|
122
|
+
try {
|
|
123
|
+
const checkCmd = 'test -f /usr/local/bin/openclaw || test -f /usr/bin/openclaw || command -v openclaw';
|
|
124
|
+
execSync(`proot-distro login ubuntu -- bash -c "${checkCmd}"`, { stdio: 'pipe', timeout: 15000 });
|
|
125
|
+
hasOpenClawInProot = true;
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let hasBionicBypassInProot = false;
|
|
130
|
+
try {
|
|
131
|
+
const prootBypassPath = path.join(PROOT_ROOTFS, 'ubuntu', 'root', '.clawdroid', 'bionic-bypass.js');
|
|
132
|
+
hasBionicBypassInProot = fs.existsSync(prootBypassPath);
|
|
133
|
+
} catch {}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
proot: hasProot,
|
|
137
|
+
ubuntu: hasUbuntu,
|
|
138
|
+
openClawInProot: hasOpenClawInProot,
|
|
139
|
+
bionicBypassInProot: hasBionicBypassInProot,
|
|
140
|
+
bionicBypass: fs.existsSync(getBypassScriptPath()),
|
|
141
|
+
nodeOptions: process.env.NODE_OPTIONS?.includes('bionic-bypass') || false,
|
|
142
|
+
openClaw: (() => {
|
|
143
|
+
try {
|
|
144
|
+
execSync('command -v openclaw', { stdio: 'pipe' });
|
|
145
|
+
return true;
|
|
146
|
+
} catch { return false; }
|
|
147
|
+
})()
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function installProot() {
|
|
152
|
+
console.log('Installing proot-distro...');
|
|
153
|
+
try {
|
|
154
|
+
execSync('pkg install -y proot-distro', { stdio: 'inherit' });
|
|
155
|
+
return true;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.error('Failed to install proot-distro:', err.message);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function installUbuntu() {
|
|
163
|
+
console.log('Installing Ubuntu in proot...');
|
|
164
|
+
try {
|
|
165
|
+
execSync('proot-distro install ubuntu', { stdio: 'inherit' });
|
|
166
|
+
return true;
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error('Failed to install Ubuntu:', err.message);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function setupProotUbuntu() {
|
|
174
|
+
console.log('Setting up Node.js and OpenClaw in Ubuntu...');
|
|
175
|
+
const setupScript = `
|
|
176
|
+
set -e
|
|
177
|
+
apt update
|
|
178
|
+
apt install -y curl wget git build-essential python3
|
|
179
|
+
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
|
180
|
+
apt install -y nodejs
|
|
181
|
+
npm install -g openclaw
|
|
182
|
+
`;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
execSync(`proot-distro login ubuntu -- bash -c '${setupScript}'`, { stdio: 'inherit' });
|
|
186
|
+
return true;
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error('Failed to setup Ubuntu:', err.message);
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function setupBionicBypassInProot() {
|
|
194
|
+
console.log('Setting up Bionic Bypass in proot Ubuntu...');
|
|
195
|
+
|
|
196
|
+
const bypassScript = `
|
|
197
|
+
const os = require('os');
|
|
198
|
+
const originalNetworkInterfaces = os.networkInterfaces;
|
|
199
|
+
os.networkInterfaces = function() {
|
|
200
|
+
try {
|
|
201
|
+
const interfaces = originalNetworkInterfaces.call(os);
|
|
202
|
+
if (interfaces && Object.keys(interfaces).length > 0) {
|
|
203
|
+
return interfaces;
|
|
204
|
+
}
|
|
205
|
+
} catch (e) {}
|
|
206
|
+
return {
|
|
207
|
+
lo: [{
|
|
208
|
+
address: '127.0.0.1',
|
|
209
|
+
netmask: '255.0.0.0',
|
|
210
|
+
family: 'IPv4',
|
|
211
|
+
mac: '00:00:00:00:00:00',
|
|
212
|
+
internal: true,
|
|
213
|
+
cidr: '127.0.0.1/8'
|
|
214
|
+
}]
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
`;
|
|
218
|
+
|
|
219
|
+
const prootBypassPath = path.join(PROOT_UBUNTU_ROOT, '.clawdroid', 'bionic-bypass.js');
|
|
220
|
+
const prootBypassDir = path.dirname(prootBypassPath);
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
if (!fs.existsSync(prootBypassDir)) {
|
|
224
|
+
fs.mkdirSync(prootBypassDir, { recursive: true });
|
|
225
|
+
}
|
|
226
|
+
fs.writeFileSync(prootBypassPath, bypassScript, 'utf8');
|
|
227
|
+
|
|
228
|
+
const prootBashrc = path.join(PROOT_UBUNTU_ROOT, '.bashrc');
|
|
229
|
+
const exportLine = 'export NODE_OPTIONS="--require /root/.clawdroid/bionic-bypass.js"';
|
|
230
|
+
|
|
231
|
+
let bashrcContent = '';
|
|
232
|
+
if (fs.existsSync(prootBashrc)) {
|
|
233
|
+
bashrcContent = fs.readFileSync(prootBashrc, 'utf8');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!bashrcContent.includes('bionic-bypass')) {
|
|
237
|
+
fs.appendFileSync(prootBashrc, `\n# ClawDroid Bionic Bypass\n${exportLine}\n`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return true;
|
|
241
|
+
} catch (err) {
|
|
242
|
+
console.error('Failed to setup Bionic Bypass in proot:', err.message);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function runInProot(command) {
|
|
248
|
+
const nodeOptions = '--require /root/.clawdroid/bionic-bypass.js';
|
|
249
|
+
return spawn('proot-distro', ['login', 'ubuntu', '--', 'bash', '-c', `export NODE_OPTIONS="${nodeOptions}" && ${command}`], {
|
|
250
|
+
stdio: 'inherit'
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function runInProotWithCallback(command, onFirstOutput) {
|
|
255
|
+
const nodeOptions = '--require /root/.clawdroid/bionic-bypass.js';
|
|
256
|
+
let firstOutput = true;
|
|
257
|
+
|
|
258
|
+
const proc = spawn('proot-distro', ['login', 'ubuntu', '--', 'bash', '-c', `export NODE_OPTIONS="${nodeOptions}" && ${command}`], {
|
|
259
|
+
stdio: ['inherit', 'pipe', 'pipe']
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
proc.stdout.on('data', (data) => {
|
|
263
|
+
if (firstOutput) {
|
|
264
|
+
firstOutput = false;
|
|
265
|
+
onFirstOutput();
|
|
266
|
+
}
|
|
267
|
+
process.stdout.write(data);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
proc.stderr.on('data', (data) => {
|
|
271
|
+
if (firstOutput) {
|
|
272
|
+
firstOutput = false;
|
|
273
|
+
onFirstOutput();
|
|
274
|
+
}
|
|
275
|
+
const str = data.toString();
|
|
276
|
+
if (!str.includes('proot warning') && !str.includes("can't sanitize")) {
|
|
277
|
+
process.stderr.write(data);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return proc;
|
|
282
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-install script - runs after npm install
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isAndroid, installBypass, getNodeOptions } from './bionic-bypass.js';
|
|
6
|
+
|
|
7
|
+
function main() {
|
|
8
|
+
console.log('\nš± ClawDroid post-install\n');
|
|
9
|
+
|
|
10
|
+
if (!isAndroid()) {
|
|
11
|
+
console.log('Not running on Android/Termux - skipping Bionic Bypass setup.');
|
|
12
|
+
console.log('You can still use this package on other systems.\n');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Install the bypass script
|
|
17
|
+
try {
|
|
18
|
+
const scriptPath = installBypass();
|
|
19
|
+
console.log(`ā Bionic Bypass installed at: ${scriptPath}`);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error('ā Failed to install Bionic Bypass:', err.message);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Show instructions
|
|
26
|
+
const nodeOptions = getNodeOptions();
|
|
27
|
+
|
|
28
|
+
console.log('\n' + 'ā'.repeat(50));
|
|
29
|
+
console.log('IMPORTANT: Add this to your shell config (~/.bashrc):');
|
|
30
|
+
console.log('ā'.repeat(50));
|
|
31
|
+
console.log(`\nexport NODE_OPTIONS="${nodeOptions}"\n`);
|
|
32
|
+
console.log('Or run: clawdroid setup');
|
|
33
|
+
console.log('ā'.repeat(50) + '\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclaw-droid",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenClaw Droid - Optimized OpenClaw AI Gateway for Android Termux",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"openclaw": "bin/openclaw"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"postinstall": "node lib/postinstall.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"openclaw",
|
|
15
|
+
"termux",
|
|
16
|
+
"android",
|
|
17
|
+
"ai",
|
|
18
|
+
"gateway",
|
|
19
|
+
"gemini",
|
|
20
|
+
"claude",
|
|
21
|
+
"openclaw-termux",
|
|
22
|
+
"openclaw-droid",
|
|
23
|
+
"clawdroid",
|
|
24
|
+
"ai-gateway",
|
|
25
|
+
"android-ai",
|
|
26
|
+
"termux-ai",
|
|
27
|
+
"openclaw-droid"
|
|
28
|
+
],
|
|
29
|
+
"author": "NOSYTLABS",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/NosytLabs/openclaw-droid.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/NosytLabs/openclaw-droid#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/NosytLabs/openclaw-droid/issues"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"os": [
|
|
43
|
+
"android",
|
|
44
|
+
"linux",
|
|
45
|
+
"win32"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"chalk": "^5.3.0",
|
|
49
|
+
"inquirer": "^9.2.12",
|
|
50
|
+
"ora": "^7.0.1"
|
|
51
|
+
}
|
|
52
|
+
}
|