@mmmbuto/nexuscli 0.5.2 → 0.5.4

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/README.md CHANGED
@@ -12,9 +12,9 @@
12
12
  NexusCLI is an experimental, ultra-light terminal cockpit designed for
13
13
  AI-assisted development workflows on Termux (Android).
14
14
 
15
- **v0.5.0** - Mobile-First AI Control Plane
15
+ **v0.5.4** - Mobile-First AI Control Plane
16
16
 
17
- Web UI wrapper for Claude Code, Codex CLI, and Gemini CLI.
17
+ Web UI wrapper for Claude Code, Codex CLI, and Gemini CLI with voice input support.
18
18
 
19
19
  ---
20
20
 
@@ -36,6 +36,8 @@ Web UI wrapper for Claude Code, Codex CLI, and Gemini CLI.
36
36
  ## Features
37
37
 
38
38
  - Multi-engine support (Claude, Codex, Gemini)
39
+ - **Voice input** (OpenAI Whisper STT)
40
+ - **Auto HTTPS** for remote microphone access
39
41
  - Mobile-first responsive UI
40
42
  - SSE streaming responses
41
43
  - Workspace management
@@ -91,6 +93,7 @@ Open browser: `http://localhost:41800`
91
93
  | `nexuscli config` | Configuration |
92
94
  | `nexuscli api` | Manage API keys |
93
95
  | `nexuscli users` | User management |
96
+ | `nexuscli setup-termux` | Bootstrap Termux (SSH, packages) |
94
97
 
95
98
  ---
96
99
 
@@ -101,13 +104,13 @@ Configure API keys for additional providers:
101
104
  ```bash
102
105
  nexuscli api list # List configured keys
103
106
  nexuscli api set deepseek <key> # DeepSeek models
104
- nexuscli api set openai <key> # Future: STT/TTS (Whisper)
107
+ nexuscli api set openai <key> # Voice input (Whisper STT)
105
108
  nexuscli api set openrouter <key> # Future: Multi-provider gateway
106
109
  nexuscli api delete <provider> # Remove key
107
110
  ```
108
111
 
109
112
  > **Note**: Claude/Codex/Gemini keys are managed by their respective CLIs.
110
- > These keys are for additional features (DeepSeek models, future STT/TTS).
113
+ > OpenAI key enables voice input via Whisper. HTTPS auto-generated for remote mic access.
111
114
 
112
115
  ---
113
116
 
package/bin/nexuscli.js CHANGED
@@ -21,6 +21,7 @@ const apiCommand = require('../lib/cli/api');
21
21
  const workspacesCommand = require('../lib/cli/workspaces');
22
22
  const usersCommand = require('../lib/cli/users');
23
23
  const uninstallCommand = require('../lib/cli/uninstall');
24
+ const setupTermuxCommand = require('../lib/cli/setup-termux');
24
25
 
25
26
  program
26
27
  .name('nexuscli')
@@ -108,6 +109,12 @@ program
108
109
  .description('Prepare for uninstallation (optional data removal)')
109
110
  .action(uninstallCommand);
110
111
 
112
+ // nexuscli setup-termux
113
+ program
114
+ .command('setup-termux')
115
+ .description('Bootstrap Termux for remote development (SSH, packages)')
116
+ .action(setupTermuxCommand);
117
+
111
118
  // Parse arguments
112
119
  program.parse();
113
120
 
@@ -0,0 +1,213 @@
1
+ /**
2
+ * nexuscli setup-termux - Bootstrap Termux for remote development
3
+ */
4
+
5
+ const chalk = require('chalk');
6
+ const { execSync, spawnSync } = require('child_process');
7
+ const os = require('os');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const { isTermux } = require('../utils/termux');
12
+ const { getConfig } = require('../config/manager');
13
+
14
+ /**
15
+ * Get local IP address
16
+ */
17
+ function getLocalIP() {
18
+ const interfaces = os.networkInterfaces();
19
+ for (const name of Object.keys(interfaces)) {
20
+ for (const iface of interfaces[name]) {
21
+ if (iface.family === 'IPv4' && !iface.internal) {
22
+ return iface.address;
23
+ }
24
+ }
25
+ }
26
+ return '127.0.0.1';
27
+ }
28
+
29
+ // Map package names to their binary names for checking
30
+ const packageBinaries = {
31
+ 'openssh': 'ssh',
32
+ 'openssl': 'openssl',
33
+ 'tmux': 'tmux',
34
+ 'git': 'git',
35
+ 'curl': 'curl',
36
+ 'wget': 'wget'
37
+ };
38
+
39
+ /**
40
+ * Check if package is installed
41
+ */
42
+ function isPackageInstalled(pkg) {
43
+ const binary = packageBinaries[pkg] || pkg;
44
+ try {
45
+ execSync(`which ${binary}`, { stdio: 'ignore' });
46
+ return true;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Install package via pkg
54
+ */
55
+ function installPackage(pkg) {
56
+ try {
57
+ console.log(chalk.gray(` Installing ${pkg}...`));
58
+ execSync(`pkg install -y ${pkg}`, { stdio: 'inherit' });
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Check if sshd is running
67
+ */
68
+ function isSshdRunning() {
69
+ try {
70
+ execSync('pgrep -x sshd', { stdio: 'ignore' });
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Start sshd
79
+ */
80
+ function startSshd() {
81
+ try {
82
+ execSync('sshd', { stdio: 'ignore' });
83
+ return true;
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Setup SSH auto-start in shell rc
91
+ */
92
+ function setupSshAutoStart() {
93
+ const home = process.env.HOME;
94
+ const rcFiles = ['.zshrc', '.bashrc'];
95
+ const autoStartLine = '# Auto-start SSH\npgrep -x sshd >/dev/null || sshd';
96
+
97
+ for (const rcFile of rcFiles) {
98
+ const rcPath = path.join(home, rcFile);
99
+ if (fs.existsSync(rcPath)) {
100
+ const content = fs.readFileSync(rcPath, 'utf8');
101
+ if (!content.includes('pgrep -x sshd')) {
102
+ fs.appendFileSync(rcPath, `\n${autoStartLine}\n`);
103
+ return rcFile;
104
+ }
105
+ return null; // Already configured
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+
111
+ /**
112
+ * Main setup command
113
+ */
114
+ async function setupTermux() {
115
+ console.log('');
116
+
117
+ // Check Termux
118
+ if (!isTermux()) {
119
+ console.log(chalk.red('This command is for Termux only.'));
120
+ console.log('');
121
+ process.exit(1);
122
+ }
123
+
124
+ console.log(chalk.bold('╔═══════════════════════════════════════════════════╗'));
125
+ console.log(chalk.bold('║ 📱 NexusCLI Termux Setup ║'));
126
+ console.log(chalk.bold('╚═══════════════════════════════════════════════════╝'));
127
+ console.log('');
128
+
129
+ // Essential packages
130
+ const packages = ['openssh', 'tmux', 'git', 'curl', 'wget', 'openssl'];
131
+
132
+ console.log(chalk.cyan('Installing packages:'));
133
+ for (const pkg of packages) {
134
+ if (isPackageInstalled(pkg)) {
135
+ console.log(chalk.green(` ✓ ${pkg}`));
136
+ } else {
137
+ const installed = installPackage(pkg);
138
+ if (installed) {
139
+ console.log(chalk.green(` ✓ ${pkg} installed`));
140
+ } else {
141
+ console.log(chalk.yellow(` ⚠ ${pkg} failed`));
142
+ }
143
+ }
144
+ }
145
+ console.log('');
146
+
147
+ // Setup SSH
148
+ console.log(chalk.cyan('Setting up SSH:'));
149
+
150
+ // Generate host keys if needed
151
+ const sshDir = path.join(process.env.PREFIX, 'etc', 'ssh');
152
+ if (!fs.existsSync(path.join(sshDir, 'ssh_host_rsa_key'))) {
153
+ console.log(chalk.gray(' Generating host keys...'));
154
+ try {
155
+ execSync('ssh-keygen -A', { stdio: 'ignore' });
156
+ console.log(chalk.green(' ✓ Host keys generated'));
157
+ } catch {
158
+ console.log(chalk.yellow(' ⚠ Could not generate host keys'));
159
+ }
160
+ }
161
+
162
+ // Start sshd
163
+ if (isSshdRunning()) {
164
+ console.log(chalk.green(' ✓ SSH server already running'));
165
+ } else {
166
+ const started = startSshd();
167
+ if (started) {
168
+ console.log(chalk.green(' ✓ SSH server started'));
169
+ } else {
170
+ console.log(chalk.yellow(' ⚠ Could not start SSH server'));
171
+ }
172
+ }
173
+
174
+ // Setup auto-start
175
+ const rcFile = setupSshAutoStart();
176
+ if (rcFile) {
177
+ console.log(chalk.green(` ✓ Auto-start added to ${rcFile}`));
178
+ } else {
179
+ console.log(chalk.gray(' Auto-start already configured'));
180
+ }
181
+ console.log('');
182
+
183
+ // Get connection info
184
+ const ip = getLocalIP();
185
+ const user = process.env.USER || 'u0_a362';
186
+ const sshPort = 8022;
187
+
188
+ let config;
189
+ try {
190
+ config = getConfig();
191
+ } catch {
192
+ config = { server: { port: 41800 } };
193
+ }
194
+ const webPort = config.server?.port || 41800;
195
+
196
+ // Output connection info
197
+ console.log(chalk.bold('╔═══════════════════════════════════════════════════╗'));
198
+ console.log(chalk.bold('║ 🔗 Connection Info ║'));
199
+ console.log(chalk.bold('╚═══════════════════════════════════════════════════╝'));
200
+ console.log('');
201
+ console.log(chalk.cyan(' SSH:'));
202
+ console.log(chalk.white(` ssh ${user}@${ip} -p ${sshPort}`));
203
+ console.log('');
204
+ console.log(chalk.cyan(' NexusCLI:'));
205
+ console.log(chalk.white(` https://${ip}:${webPort}`));
206
+ console.log('');
207
+ console.log(chalk.gray(' Note: Set password with: passwd'));
208
+ console.log('');
209
+
210
+ process.exit(0);
211
+ }
212
+
213
+ module.exports = setupTermux;
@@ -169,50 +169,62 @@ async function start() {
169
169
  const certDir = path.join(process.env.HOME || '', '.nexuscli', 'certs');
170
170
  const certPath = path.join(certDir, 'cert.pem');
171
171
  const keyPath = path.join(certDir, 'key.pem');
172
- const useHttps = fs.existsSync(certPath) && fs.existsSync(keyPath);
172
+ const hasHttpsCerts = fs.existsSync(certPath) && fs.existsSync(keyPath);
173
173
 
174
- let server;
175
- let protocol = 'http';
174
+ // Always start HTTP server on main port
175
+ const httpServer = http.createServer(app);
176
176
 
177
- if (useHttps) {
177
+ // Start HTTPS server on PORT+1 if certificates exist (for remote mic access)
178
+ let httpsServer = null;
179
+ const HTTPS_PORT = parseInt(PORT) + 1;
180
+
181
+ if (hasHttpsCerts) {
178
182
  try {
179
183
  const httpsOptions = {
180
184
  key: fs.readFileSync(keyPath),
181
185
  cert: fs.readFileSync(certPath)
182
186
  };
183
- server = https.createServer(httpsOptions, app);
184
- protocol = 'https';
185
- console.log('[Startup] HTTPS enabled - certificates found');
187
+ httpsServer = https.createServer(httpsOptions, app);
188
+ console.log('[Startup] HTTPS certificates found');
186
189
  } catch (err) {
187
- console.warn('[Startup] Failed to load certificates, falling back to HTTP:', err.message);
188
- server = http.createServer(app);
190
+ console.warn('[Startup] Failed to load certificates:', err.message);
189
191
  }
190
- } else {
191
- server = http.createServer(app);
192
- console.log('[Startup] HTTP mode - no certificates found');
193
- console.log('[Startup] Run: ./scripts/setup-https.sh to enable HTTPS');
194
192
  }
195
193
 
196
- server.listen(PORT, () => {
194
+ httpServer.listen(PORT, () => {
197
195
  console.log('');
198
196
  console.log('╔══════════════════════════════════════════════╗');
199
197
  console.log('║ 🚀 NexusCLI Backend ║');
200
198
  console.log('╚══════════════════════════════════════════════╝');
201
199
  console.log('');
202
- console.log(`✅ Server running on ${protocol}://localhost:${PORT}`);
203
- if (useHttps) {
204
- console.log(`🔒 HTTPS enabled - secure connection`);
200
+ console.log(`✅ HTTP: http://localhost:${PORT}`);
201
+
202
+ if (httpsServer) {
203
+ httpsServer.listen(HTTPS_PORT, () => {
204
+ console.log(`🔒 HTTPS: https://localhost:${HTTPS_PORT} (for remote mic)`);
205
+ console.log('');
206
+ console.log('Endpoints:');
207
+ console.log(` GET /health (public)`);
208
+ console.log(` POST /api/v1/auth/login (public)`);
209
+ console.log(` GET /api/v1/auth/me (protected)`);
210
+ console.log(` GET /api/v1/conversations (protected)`);
211
+ console.log(` POST /api/v1/conversations (protected)`);
212
+ console.log(` POST /api/v1/jobs (protected)`);
213
+ console.log(` GET /api/v1/jobs/:id/stream (protected, SSE)`);
214
+ console.log('');
215
+ });
216
+ } else {
217
+ console.log('');
218
+ console.log('Endpoints:');
219
+ console.log(` GET /health (public)`);
220
+ console.log(` POST /api/v1/auth/login (public)`);
221
+ console.log(` GET /api/v1/auth/me (protected)`);
222
+ console.log(` GET /api/v1/conversations (protected)`);
223
+ console.log(` POST /api/v1/conversations (protected)`);
224
+ console.log(` POST /api/v1/jobs (protected)`);
225
+ console.log(` GET /api/v1/jobs/:id/stream (protected, SSE)`);
226
+ console.log('');
205
227
  }
206
- console.log('');
207
- console.log('Endpoints:');
208
- console.log(` GET /health (public)`);
209
- console.log(` POST /api/v1/auth/login (public)`);
210
- console.log(` GET /api/v1/auth/me (protected)`);
211
- console.log(` GET /api/v1/conversations (protected)`);
212
- console.log(` POST /api/v1/conversations (protected)`);
213
- console.log(` POST /api/v1/jobs (protected)`);
214
- console.log(` GET /api/v1/jobs/:id/stream (protected, SSE)`);
215
- console.log('');
216
228
  });
217
229
  }
218
230
 
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscli",
3
- "version": "0.5.2",
4
-
3
+ "version": "0.5.4",
5
4
  "description": "NexusCLI - TRI CLI Control Plane (Claude/Codex/Gemini)",
6
5
  "main": "lib/server/server.js",
7
6
  "bin": {