@mmmbuto/nexuscli 0.5.4 → 0.5.6
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 +20 -106
- package/bin/nexuscli.js +6 -6
- package/lib/server/.env.example +1 -1
- package/lib/server/db.js.old +225 -0
- package/lib/server/docs/API_WRAPPER_CONTRACT.md +682 -0
- package/lib/server/docs/ARCHITECTURE.md +441 -0
- package/lib/server/docs/DATABASE_SCHEMA.md +783 -0
- package/lib/server/docs/DESIGN_PRINCIPLES.md +598 -0
- package/lib/server/docs/NEXUSCHAT_ANALYSIS.md +488 -0
- package/lib/server/docs/PIPELINE_INTEGRATION.md +636 -0
- package/lib/server/docs/README.md +272 -0
- package/lib/server/docs/UI_DESIGN.md +916 -0
- package/lib/server/routes/codex.js +12 -3
- package/lib/server/routes/gemini.js +13 -3
- package/lib/server/services/codex-output-parser.js +8 -0
- package/lib/server/services/codex-wrapper.js +27 -20
- package/lib/server/services/context-bridge.js +6 -4
- package/lib/server/services/gemini-wrapper.js +14 -6
- package/lib/server/services/session-manager.js +43 -1
- package/lib/server/services/workspace-manager.js +1 -1
- package/lib/server/tests/performance.test.js +1 -1
- package/lib/server/tests/services.test.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,48 +1,8 @@
|
|
|
1
|
-
# NexusCLI
|
|
1
|
+
# NexusCLI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<img src=".github/header/header.png" width="900" />
|
|
5
|
-
</p>
|
|
3
|
+
**TRI CLI v0.4.0** - Termux Mobile First AI Control Plane
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
NexusCLI is an experimental, ultra-light terminal cockpit designed for
|
|
13
|
-
AI-assisted development workflows on Termux (Android).
|
|
14
|
-
|
|
15
|
-
**v0.5.4** - Mobile-First AI Control Plane
|
|
16
|
-
|
|
17
|
-
Web UI wrapper for Claude Code, Codex CLI, and Gemini CLI with voice input support.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
[](https://www.npmjs.com/package/@mmmbuto/nexuscli)
|
|
22
|
-
[](https://www.npmjs.com/package/@mmmbuto/nexuscli)
|
|
23
|
-
[](https://ko-fi.com/dionanos)
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Screenshots
|
|
28
|
-
|
|
29
|
-
<p align="center">
|
|
30
|
-
<img src="docs/assets/screenshots/nexuscli-multilang-preview.png" width="45%" />
|
|
31
|
-
<img src="docs/assets/screenshots/nexuscli-mobile-terminal.png" width="45%" />
|
|
32
|
-
</p>
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Features
|
|
37
|
-
|
|
38
|
-
- Multi-engine support (Claude, Codex, Gemini)
|
|
39
|
-
- **Voice input** (OpenAI Whisper STT)
|
|
40
|
-
- **Auto HTTPS** for remote microphone access
|
|
41
|
-
- Mobile-first responsive UI
|
|
42
|
-
- SSE streaming responses
|
|
43
|
-
- Workspace management
|
|
44
|
-
- Conversation history
|
|
45
|
-
- Model selector with think mode toggle
|
|
5
|
+
Web UI for Claude Code, Codex CLI, and Gemini CLI.
|
|
46
6
|
|
|
47
7
|
## Supported Engines
|
|
48
8
|
|
|
@@ -52,16 +12,23 @@ Web UI wrapper for Claude Code, Codex CLI, and Gemini CLI with voice input suppo
|
|
|
52
12
|
| **Codex** | GPT-5.1, GPT-5.1 Codex (Mini/Max) | OpenAI |
|
|
53
13
|
| **Gemini** | Gemini 3 Pro Preview | Google |
|
|
54
14
|
|
|
55
|
-
---
|
|
56
|
-
|
|
57
15
|
## Install
|
|
58
16
|
|
|
17
|
+
**Private package** - Install via git:
|
|
18
|
+
|
|
59
19
|
```bash
|
|
60
|
-
# From
|
|
61
|
-
npm install -g @
|
|
20
|
+
# From VPS (recommended)
|
|
21
|
+
npm install -g git+ssh://dag@cloud.alpacalibre.com/home/dag/git_repos/NexusCLI.git
|
|
62
22
|
|
|
63
|
-
# From
|
|
64
|
-
npm install -g
|
|
23
|
+
# From VPSHome backup
|
|
24
|
+
npm install -g git+ssh://calner@192.168.10.10/home/calner/git_repos/NexusCLI.git
|
|
25
|
+
|
|
26
|
+
# Local clone install
|
|
27
|
+
cd ~/Dev/NexusCLI && npm install -g .
|
|
28
|
+
|
|
29
|
+
# Termux manual install
|
|
30
|
+
cd ~/Dev/NexusCLI
|
|
31
|
+
./scripts/install-termux.sh
|
|
65
32
|
```
|
|
66
33
|
|
|
67
34
|
## Setup
|
|
@@ -78,8 +45,6 @@ nexuscli start
|
|
|
78
45
|
|
|
79
46
|
Open browser: `http://localhost:41800`
|
|
80
47
|
|
|
81
|
-
---
|
|
82
|
-
|
|
83
48
|
## Commands
|
|
84
49
|
|
|
85
50
|
| Command | Description |
|
|
@@ -91,28 +56,6 @@ Open browser: `http://localhost:41800`
|
|
|
91
56
|
| `nexuscli engines` | Manage AI engines |
|
|
92
57
|
| `nexuscli workspaces` | Manage workspaces |
|
|
93
58
|
| `nexuscli config` | Configuration |
|
|
94
|
-
| `nexuscli api` | Manage API keys |
|
|
95
|
-
| `nexuscli users` | User management |
|
|
96
|
-
| `nexuscli setup-termux` | Bootstrap Termux (SSH, packages) |
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## API Keys
|
|
101
|
-
|
|
102
|
-
Configure API keys for additional providers:
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
nexuscli api list # List configured keys
|
|
106
|
-
nexuscli api set deepseek <key> # DeepSeek models
|
|
107
|
-
nexuscli api set openai <key> # Voice input (Whisper STT)
|
|
108
|
-
nexuscli api set openrouter <key> # Future: Multi-provider gateway
|
|
109
|
-
nexuscli api delete <provider> # Remove key
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
> **Note**: Claude/Codex/Gemini keys are managed by their respective CLIs.
|
|
113
|
-
> OpenAI key enables voice input via Whisper. HTTPS auto-generated for remote mic access.
|
|
114
|
-
|
|
115
|
-
---
|
|
116
59
|
|
|
117
60
|
## Requirements
|
|
118
61
|
|
|
@@ -121,31 +64,7 @@ nexuscli api delete <provider> # Remove key
|
|
|
121
64
|
- Claude Code CLI (`claude`)
|
|
122
65
|
- Codex CLI (`codex`)
|
|
123
66
|
- Gemini CLI (`gemini`)
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Termux-First Architecture
|
|
128
|
-
|
|
129
|
-
NexusCLI is designed primarily for **Termux** on Android devices.
|
|
130
|
-
|
|
131
|
-
### Stack
|
|
132
|
-
|
|
133
|
-
- **Termux** - primary runtime environment
|
|
134
|
-
- **tmux** - session management
|
|
135
|
-
- **Node.js + SSE** - lightweight backend
|
|
136
|
-
- **React** - minimal UI
|
|
137
|
-
|
|
138
|
-
### Purpose
|
|
139
|
-
|
|
140
|
-
This project exists to study:
|
|
141
|
-
|
|
142
|
-
- terminal-driven AI orchestration
|
|
143
|
-
- ultra-light architectures for constrained devices
|
|
144
|
-
- mobile development workflows
|
|
145
|
-
|
|
146
|
-
It is a **research and learning tool**.
|
|
147
|
-
|
|
148
|
-
---
|
|
67
|
+
- SSH access to git repos (for installation)
|
|
149
68
|
|
|
150
69
|
## API Endpoints
|
|
151
70
|
|
|
@@ -157,14 +76,12 @@ It is a **research and learning tool**.
|
|
|
157
76
|
| `GET /api/v1/models` | All | List available models |
|
|
158
77
|
| `GET /health` | - | Health check |
|
|
159
78
|
|
|
160
|
-
---
|
|
161
|
-
|
|
162
79
|
## Development
|
|
163
80
|
|
|
164
81
|
```bash
|
|
165
82
|
# Clone
|
|
166
|
-
git clone
|
|
167
|
-
cd
|
|
83
|
+
git clone dag@cloud.alpacalibre.com:git_repos/NexusCLI.git
|
|
84
|
+
cd NexusCLI
|
|
168
85
|
|
|
169
86
|
# Install deps
|
|
170
87
|
npm install
|
|
@@ -174,9 +91,6 @@ cd frontend && npm install && npm run build && cd ..
|
|
|
174
91
|
npm run dev
|
|
175
92
|
```
|
|
176
93
|
|
|
177
|
-
---
|
|
178
|
-
|
|
179
94
|
## License
|
|
180
95
|
|
|
181
|
-
MIT
|
|
182
|
-
See `LICENSE` for details.
|
|
96
|
+
MIT
|
package/bin/nexuscli.js
CHANGED
|
@@ -103,18 +103,18 @@ program
|
|
|
103
103
|
.option('-a, --admin', 'Create as admin user')
|
|
104
104
|
.action(usersCommand);
|
|
105
105
|
|
|
106
|
+
// nexuscli setup-termux
|
|
107
|
+
program
|
|
108
|
+
.command('setup-termux')
|
|
109
|
+
.description('Bootstrap Termux: install packages, setup SSH, show connection info')
|
|
110
|
+
.action(setupTermuxCommand);
|
|
111
|
+
|
|
106
112
|
// nexuscli uninstall
|
|
107
113
|
program
|
|
108
114
|
.command('uninstall')
|
|
109
115
|
.description('Prepare for uninstallation (optional data removal)')
|
|
110
116
|
.action(uninstallCommand);
|
|
111
117
|
|
|
112
|
-
// nexuscli setup-termux
|
|
113
|
-
program
|
|
114
|
-
.command('setup-termux')
|
|
115
|
-
.description('Bootstrap Termux for remote development (SSH, packages)')
|
|
116
|
-
.action(setupTermuxCommand);
|
|
117
|
-
|
|
118
118
|
// Parse arguments
|
|
119
119
|
program.parse();
|
|
120
120
|
|
package/lib/server/.env.example
CHANGED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
const initSqlJs = require('sql.js');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
// Detect environment (Linux vs Termux)
|
|
6
|
+
const isTermux = process.env.PREFIX?.includes('com.termux');
|
|
7
|
+
|
|
8
|
+
// Database directory
|
|
9
|
+
const dbDir = isTermux
|
|
10
|
+
? path.join(process.env.HOME, '.nexuscli')
|
|
11
|
+
: process.env.NEXUSCLI_DB_DIR || '/var/lib/nexuscli';
|
|
12
|
+
|
|
13
|
+
// Database file path
|
|
14
|
+
const dbPath = path.join(dbDir, 'nexuscli.db');
|
|
15
|
+
|
|
16
|
+
// Ensure directory exists
|
|
17
|
+
if (!fs.existsSync(dbDir)) {
|
|
18
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
19
|
+
console.log(`✅ Created database directory: ${dbDir}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let db = null;
|
|
23
|
+
|
|
24
|
+
// Initialize database
|
|
25
|
+
async function initDb() {
|
|
26
|
+
const SQL = await initSqlJs();
|
|
27
|
+
|
|
28
|
+
// Load existing database or create new
|
|
29
|
+
if (fs.existsSync(dbPath)) {
|
|
30
|
+
const buffer = fs.readFileSync(dbPath);
|
|
31
|
+
db = new SQL.Database(buffer);
|
|
32
|
+
console.log(`✅ Database loaded: ${dbPath}`);
|
|
33
|
+
} else {
|
|
34
|
+
db = new SQL.Database();
|
|
35
|
+
console.log(`✅ Database created: ${dbPath}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Initialize schema
|
|
39
|
+
initSchema();
|
|
40
|
+
|
|
41
|
+
// Auto-save every 5 seconds
|
|
42
|
+
setInterval(saveDb, 5000);
|
|
43
|
+
|
|
44
|
+
return db;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Save database to file
|
|
48
|
+
function saveDb() {
|
|
49
|
+
if (!db) return;
|
|
50
|
+
const data = db.export();
|
|
51
|
+
const buffer = Buffer.from(data);
|
|
52
|
+
fs.writeFileSync(dbPath, buffer);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Initialize schema
|
|
56
|
+
function initSchema() {
|
|
57
|
+
db.run(`
|
|
58
|
+
-- Conversations table
|
|
59
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
title TEXT NOT NULL,
|
|
62
|
+
created_at INTEGER NOT NULL,
|
|
63
|
+
updated_at INTEGER NOT NULL,
|
|
64
|
+
metadata TEXT
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_updated_at
|
|
68
|
+
ON conversations(updated_at DESC);
|
|
69
|
+
|
|
70
|
+
-- Messages table
|
|
71
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
72
|
+
id TEXT PRIMARY KEY,
|
|
73
|
+
conversation_id TEXT NOT NULL,
|
|
74
|
+
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
|
|
75
|
+
content TEXT NOT NULL,
|
|
76
|
+
created_at INTEGER NOT NULL,
|
|
77
|
+
metadata TEXT,
|
|
78
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id
|
|
82
|
+
ON messages(conversation_id);
|
|
83
|
+
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_messages_created_at
|
|
85
|
+
ON messages(created_at ASC);
|
|
86
|
+
|
|
87
|
+
-- Jobs table
|
|
88
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
89
|
+
id TEXT PRIMARY KEY,
|
|
90
|
+
conversation_id TEXT,
|
|
91
|
+
message_id TEXT,
|
|
92
|
+
node_id TEXT NOT NULL,
|
|
93
|
+
tool TEXT NOT NULL,
|
|
94
|
+
command TEXT NOT NULL,
|
|
95
|
+
status TEXT NOT NULL CHECK(status IN ('queued', 'executing', 'completed', 'failed', 'cancelled')),
|
|
96
|
+
exit_code INTEGER,
|
|
97
|
+
stdout TEXT,
|
|
98
|
+
stderr TEXT,
|
|
99
|
+
duration INTEGER,
|
|
100
|
+
created_at INTEGER NOT NULL,
|
|
101
|
+
started_at INTEGER,
|
|
102
|
+
completed_at INTEGER,
|
|
103
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE SET NULL,
|
|
104
|
+
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE SET NULL
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_conversation_id
|
|
108
|
+
ON jobs(conversation_id);
|
|
109
|
+
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_status
|
|
111
|
+
ON jobs(status);
|
|
112
|
+
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_created_at
|
|
114
|
+
ON jobs(created_at DESC);
|
|
115
|
+
|
|
116
|
+
-- Users table
|
|
117
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
118
|
+
id TEXT PRIMARY KEY,
|
|
119
|
+
username TEXT UNIQUE NOT NULL,
|
|
120
|
+
password_hash TEXT NOT NULL,
|
|
121
|
+
role TEXT NOT NULL DEFAULT 'user' CHECK(role IN ('admin', 'user')),
|
|
122
|
+
is_locked INTEGER NOT NULL DEFAULT 0,
|
|
123
|
+
failed_attempts INTEGER NOT NULL DEFAULT 0,
|
|
124
|
+
last_failed_attempt INTEGER,
|
|
125
|
+
locked_until INTEGER,
|
|
126
|
+
created_at INTEGER NOT NULL,
|
|
127
|
+
last_login INTEGER
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_users_username
|
|
131
|
+
ON users(username);
|
|
132
|
+
|
|
133
|
+
-- Login attempts table (for rate limiting)
|
|
134
|
+
CREATE TABLE IF NOT EXISTS login_attempts (
|
|
135
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
136
|
+
ip_address TEXT NOT NULL,
|
|
137
|
+
username TEXT,
|
|
138
|
+
success INTEGER NOT NULL DEFAULT 0,
|
|
139
|
+
timestamp INTEGER NOT NULL
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_login_attempts_ip
|
|
143
|
+
ON login_attempts(ip_address, timestamp DESC);
|
|
144
|
+
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_login_attempts_timestamp
|
|
146
|
+
ON login_attempts(timestamp DESC);
|
|
147
|
+
|
|
148
|
+
-- Nodes table (optional - for multi-node setups)
|
|
149
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
150
|
+
id TEXT PRIMARY KEY,
|
|
151
|
+
hostname TEXT NOT NULL,
|
|
152
|
+
ip_address TEXT,
|
|
153
|
+
status TEXT NOT NULL CHECK(status IN ('online', 'offline', 'error')),
|
|
154
|
+
capabilities TEXT,
|
|
155
|
+
last_heartbeat INTEGER,
|
|
156
|
+
created_at INTEGER NOT NULL
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_status
|
|
160
|
+
ON nodes(status);
|
|
161
|
+
`);
|
|
162
|
+
|
|
163
|
+
console.log('✅ Database schema initialized');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Prepare statement (sql.js uses db.prepare)
|
|
167
|
+
function prepare(sql) {
|
|
168
|
+
const stmt = db.prepare(sql);
|
|
169
|
+
return {
|
|
170
|
+
run: (...params) => {
|
|
171
|
+
stmt.bind(params);
|
|
172
|
+
stmt.step();
|
|
173
|
+
stmt.reset();
|
|
174
|
+
saveDb(); // Auto-save on write
|
|
175
|
+
},
|
|
176
|
+
get: (...params) => {
|
|
177
|
+
stmt.bind(params);
|
|
178
|
+
const result = stmt.step() ? stmt.getAsObject() : null;
|
|
179
|
+
stmt.reset();
|
|
180
|
+
return result;
|
|
181
|
+
},
|
|
182
|
+
all: (...params) => {
|
|
183
|
+
stmt.bind(params);
|
|
184
|
+
const results = [];
|
|
185
|
+
while (stmt.step()) {
|
|
186
|
+
results.push(stmt.getAsObject());
|
|
187
|
+
}
|
|
188
|
+
stmt.reset();
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Graceful shutdown
|
|
195
|
+
process.on('exit', () => {
|
|
196
|
+
if (db) {
|
|
197
|
+
saveDb();
|
|
198
|
+
db.close();
|
|
199
|
+
console.log('✅ Database connection closed');
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
process.on('SIGINT', () => {
|
|
204
|
+
if (db) {
|
|
205
|
+
saveDb();
|
|
206
|
+
db.close();
|
|
207
|
+
console.log('✅ Database connection closed (SIGINT)');
|
|
208
|
+
}
|
|
209
|
+
process.exit(0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
process.on('SIGTERM', () => {
|
|
213
|
+
if (db) {
|
|
214
|
+
saveDb();
|
|
215
|
+
db.close();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Export db object (initialized async)
|
|
220
|
+
module.exports = {
|
|
221
|
+
initDb,
|
|
222
|
+
getDb: () => db,
|
|
223
|
+
prepare,
|
|
224
|
+
saveDb
|
|
225
|
+
};
|