@mmmbuto/nexuscli 0.5.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 +172 -0
- package/bin/nexuscli.js +117 -0
- package/frontend/dist/apple-touch-icon.png +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/frontend/dist/assets/index-Bn_l1e6e.css +1 -0
- package/frontend/dist/assets/index-CikJbUR5.js +8617 -0
- package/frontend/dist/browserconfig.xml +12 -0
- package/frontend/dist/favicon-16x16.png +0 -0
- package/frontend/dist/favicon-32x32.png +0 -0
- package/frontend/dist/favicon-48x48.png +0 -0
- package/frontend/dist/favicon.ico +0 -0
- package/frontend/dist/icon-192.png +0 -0
- package/frontend/dist/icon-512.png +0 -0
- package/frontend/dist/icon-maskable-192.png +0 -0
- package/frontend/dist/icon-maskable-512.png +0 -0
- package/frontend/dist/index.html +79 -0
- package/frontend/dist/manifest.json +75 -0
- package/frontend/dist/sw.js +122 -0
- package/frontend/package.json +28 -0
- package/lib/cli/api.js +156 -0
- package/lib/cli/boot.js +172 -0
- package/lib/cli/config.js +185 -0
- package/lib/cli/engines.js +257 -0
- package/lib/cli/init.js +660 -0
- package/lib/cli/logs.js +72 -0
- package/lib/cli/start.js +220 -0
- package/lib/cli/status.js +187 -0
- package/lib/cli/stop.js +64 -0
- package/lib/cli/uninstall.js +194 -0
- package/lib/cli/users.js +295 -0
- package/lib/cli/workspaces.js +337 -0
- package/lib/config/manager.js +233 -0
- package/lib/server/.env.example +20 -0
- package/lib/server/db/adapter.js +314 -0
- package/lib/server/db/drivers/better-sqlite3.js +38 -0
- package/lib/server/db/drivers/sql-js.js +75 -0
- package/lib/server/db/migrate.js +174 -0
- package/lib/server/db/migrations/001_ultra_light_schema.sql +96 -0
- package/lib/server/db/migrations/002_session_conversation_mapping.sql +19 -0
- package/lib/server/db/migrations/003_message_engine_tracking.sql +18 -0
- package/lib/server/db/migrations/004_performance_indexes.sql +16 -0
- package/lib/server/db.js +2 -0
- package/lib/server/lib/cli-wrapper.js +164 -0
- package/lib/server/lib/output-parser.js +132 -0
- package/lib/server/lib/pty-adapter.js +57 -0
- package/lib/server/middleware/auth.js +103 -0
- package/lib/server/models/Conversation.js +259 -0
- package/lib/server/models/Message.js +228 -0
- package/lib/server/models/User.js +115 -0
- package/lib/server/package-lock.json +5895 -0
- package/lib/server/routes/auth.js +168 -0
- package/lib/server/routes/chat.js +206 -0
- package/lib/server/routes/codex.js +205 -0
- package/lib/server/routes/conversations.js +224 -0
- package/lib/server/routes/gemini.js +228 -0
- package/lib/server/routes/jobs.js +317 -0
- package/lib/server/routes/messages.js +60 -0
- package/lib/server/routes/models.js +198 -0
- package/lib/server/routes/sessions.js +285 -0
- package/lib/server/routes/upload.js +134 -0
- package/lib/server/routes/wake-lock.js +95 -0
- package/lib/server/routes/workspace.js +80 -0
- package/lib/server/routes/workspaces.js +142 -0
- package/lib/server/scripts/cleanup-ghost-sessions.js +71 -0
- package/lib/server/scripts/seed-users.js +37 -0
- package/lib/server/scripts/test-history-access.js +50 -0
- package/lib/server/server.js +227 -0
- package/lib/server/services/cache.js +85 -0
- package/lib/server/services/claude-wrapper.js +312 -0
- package/lib/server/services/cli-loader.js +384 -0
- package/lib/server/services/codex-output-parser.js +277 -0
- package/lib/server/services/codex-wrapper.js +224 -0
- package/lib/server/services/context-bridge.js +289 -0
- package/lib/server/services/gemini-output-parser.js +398 -0
- package/lib/server/services/gemini-wrapper.js +249 -0
- package/lib/server/services/history-sync.js +407 -0
- package/lib/server/services/output-parser.js +415 -0
- package/lib/server/services/session-manager.js +465 -0
- package/lib/server/services/summary-generator.js +259 -0
- package/lib/server/services/workspace-manager.js +516 -0
- package/lib/server/tests/history-sync.test.js +90 -0
- package/lib/server/tests/integration-session-sync.test.js +151 -0
- package/lib/server/tests/integration.test.js +76 -0
- package/lib/server/tests/performance.test.js +118 -0
- package/lib/server/tests/services.test.js +160 -0
- package/lib/setup/postinstall.js +216 -0
- package/lib/utils/paths.js +107 -0
- package/lib/utils/termux.js +145 -0
- package/package.json +82 -0
package/lib/cli/logs.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nexuscli logs - View server logs
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const { PATHS } = require('../utils/paths');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Main logs command
|
|
13
|
+
*/
|
|
14
|
+
async function logs(options) {
|
|
15
|
+
console.log('');
|
|
16
|
+
|
|
17
|
+
// Check if log file exists
|
|
18
|
+
if (!fs.existsSync(PATHS.SERVER_LOG)) {
|
|
19
|
+
console.log(chalk.yellow('No log file found.'));
|
|
20
|
+
console.log(chalk.gray(`Expected: ${PATHS.SERVER_LOG}`));
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log('Server may not have been started as daemon yet.');
|
|
23
|
+
console.log(`Run ${chalk.cyan('nexuscli start --daemon')} first.`);
|
|
24
|
+
console.log('');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const lines = parseInt(options.lines) || 50;
|
|
29
|
+
|
|
30
|
+
if (options.follow) {
|
|
31
|
+
// Follow mode (tail -f)
|
|
32
|
+
console.log(chalk.cyan(`Following logs (Ctrl+C to exit)...`));
|
|
33
|
+
console.log(chalk.gray(`File: ${PATHS.SERVER_LOG}`));
|
|
34
|
+
console.log('');
|
|
35
|
+
|
|
36
|
+
const tail = spawn('tail', ['-f', '-n', String(lines), PATHS.SERVER_LOG], {
|
|
37
|
+
stdio: 'inherit'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Handle Ctrl+C
|
|
41
|
+
process.on('SIGINT', () => {
|
|
42
|
+
tail.kill();
|
|
43
|
+
console.log('');
|
|
44
|
+
process.exit(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Wait for tail to exit
|
|
48
|
+
await new Promise((resolve) => {
|
|
49
|
+
tail.on('exit', resolve);
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
// Static mode (show last N lines)
|
|
53
|
+
console.log(chalk.cyan(`Last ${lines} lines:`));
|
|
54
|
+
console.log(chalk.gray(`File: ${PATHS.SERVER_LOG}`));
|
|
55
|
+
console.log('');
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const content = fs.readFileSync(PATHS.SERVER_LOG, 'utf8');
|
|
59
|
+
const allLines = content.split('\n');
|
|
60
|
+
const lastLines = allLines.slice(-lines).join('\n');
|
|
61
|
+
console.log(lastLines);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.log(chalk.red(`Error reading logs: ${err.message}`));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(chalk.gray(`Use ${chalk.white('nexuscli logs -f')} to follow live`));
|
|
68
|
+
console.log('');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = logs;
|
package/lib/cli/start.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nexuscli start - Start the server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { spawn, execSync } = require('child_process');
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
|
|
11
|
+
const { isInitialized, getConfig } = require('../config/manager');
|
|
12
|
+
const { PATHS } = require('../utils/paths');
|
|
13
|
+
const { isTermux, acquireWakeLock, sendNotification } = require('../utils/termux');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if server is already running
|
|
17
|
+
*/
|
|
18
|
+
function isServerRunning() {
|
|
19
|
+
if (!fs.existsSync(PATHS.PID_FILE)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const pid = parseInt(fs.readFileSync(PATHS.PID_FILE, 'utf8').trim());
|
|
25
|
+
|
|
26
|
+
// Check if process exists
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
return pid;
|
|
29
|
+
} catch {
|
|
30
|
+
// Process doesn't exist, clean up stale PID file
|
|
31
|
+
try {
|
|
32
|
+
fs.unlinkSync(PATHS.PID_FILE);
|
|
33
|
+
} catch {}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Write PID file
|
|
40
|
+
*/
|
|
41
|
+
function writePidFile(pid) {
|
|
42
|
+
fs.writeFileSync(PATHS.PID_FILE, String(pid));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Open URL in browser (Termux-only)
|
|
47
|
+
*/
|
|
48
|
+
function openBrowser(url) {
|
|
49
|
+
try {
|
|
50
|
+
// Termux: use am start (Android intent) - most reliable
|
|
51
|
+
try {
|
|
52
|
+
execSync(`am start -a android.intent.action.VIEW -d "${url}"`, { stdio: 'ignore' });
|
|
53
|
+
} catch {
|
|
54
|
+
// Fallback to termux-open-url if am fails
|
|
55
|
+
const child = spawn('termux-open-url', [url], {
|
|
56
|
+
detached: true,
|
|
57
|
+
stdio: 'ignore'
|
|
58
|
+
});
|
|
59
|
+
child.unref();
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Ask yes/no question
|
|
69
|
+
*/
|
|
70
|
+
function askYesNo(question) {
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
const rl = readline.createInterface({
|
|
73
|
+
input: process.stdin,
|
|
74
|
+
output: process.stdout
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
rl.question(question, (answer) => {
|
|
78
|
+
rl.close();
|
|
79
|
+
const normalized = answer.trim().toLowerCase();
|
|
80
|
+
resolve(normalized === 'y' || normalized === 'yes' || normalized === 's' || normalized === 'si');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Start server in foreground
|
|
87
|
+
*/
|
|
88
|
+
function startForeground(config) {
|
|
89
|
+
const serverPath = path.join(__dirname, '..', 'server', 'server.js');
|
|
90
|
+
|
|
91
|
+
// Set environment variables from config
|
|
92
|
+
process.env.PORT = config.server.port;
|
|
93
|
+
process.env.JWT_SECRET = config.auth.jwt_secret;
|
|
94
|
+
process.env.NEXUSCLI_AUTO_SEED = '0'; // Don't auto-seed, we have config
|
|
95
|
+
|
|
96
|
+
// Acquire wake lock on Termux
|
|
97
|
+
if (isTermux() && config.termux.wake_lock) {
|
|
98
|
+
acquireWakeLock();
|
|
99
|
+
console.log(chalk.green(' ✓ Wake-lock acquired'));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Start server
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(chalk.cyan(`Starting NexusCLI on port ${config.server.port}...`));
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
require(serverPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Start server as daemon
|
|
112
|
+
*/
|
|
113
|
+
function startDaemon(config) {
|
|
114
|
+
const serverPath = path.join(__dirname, '..', 'server', 'server.js');
|
|
115
|
+
|
|
116
|
+
// Ensure log directory exists
|
|
117
|
+
if (!fs.existsSync(PATHS.LOGS_DIR)) {
|
|
118
|
+
fs.mkdirSync(PATHS.LOGS_DIR, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Open log file
|
|
122
|
+
const logFile = fs.openSync(PATHS.SERVER_LOG, 'a');
|
|
123
|
+
|
|
124
|
+
// Spawn detached process
|
|
125
|
+
const child = spawn('node', [serverPath], {
|
|
126
|
+
detached: true,
|
|
127
|
+
stdio: ['ignore', logFile, logFile],
|
|
128
|
+
env: {
|
|
129
|
+
...process.env,
|
|
130
|
+
PORT: config.server.port,
|
|
131
|
+
JWT_SECRET: config.auth.jwt_secret,
|
|
132
|
+
NEXUSCLI_AUTO_SEED: '0',
|
|
133
|
+
NEXUSCLI_DAEMON: '1'
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Write PID file
|
|
138
|
+
writePidFile(child.pid);
|
|
139
|
+
|
|
140
|
+
// Unref to allow parent to exit
|
|
141
|
+
child.unref();
|
|
142
|
+
|
|
143
|
+
// Acquire wake lock on Termux
|
|
144
|
+
if (isTermux() && config.termux.wake_lock) {
|
|
145
|
+
acquireWakeLock();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Send notification on Termux
|
|
149
|
+
if (isTermux() && config.termux.notifications) {
|
|
150
|
+
sendNotification('NexusCLI', `Server running on port ${config.server.port}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return child.pid;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Main start command
|
|
158
|
+
*/
|
|
159
|
+
async function start(options) {
|
|
160
|
+
console.log('');
|
|
161
|
+
|
|
162
|
+
// Check if initialized
|
|
163
|
+
if (!isInitialized()) {
|
|
164
|
+
console.log(chalk.yellow('NexusCLI is not configured.'));
|
|
165
|
+
console.log(`Run ${chalk.cyan('nexuscli init')} first.`);
|
|
166
|
+
console.log('');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if already running
|
|
171
|
+
const runningPid = isServerRunning();
|
|
172
|
+
if (runningPid) {
|
|
173
|
+
console.log(chalk.yellow(`Server already running (PID: ${runningPid})`));
|
|
174
|
+
console.log(`Run ${chalk.cyan('nexuscli stop')} to stop it.`);
|
|
175
|
+
console.log('');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const config = getConfig();
|
|
180
|
+
const port = options.port || config.server.port;
|
|
181
|
+
config.server.port = port;
|
|
182
|
+
|
|
183
|
+
const url = `http://localhost:${port}`;
|
|
184
|
+
|
|
185
|
+
// Foreground mode (--foreground flag)
|
|
186
|
+
if (options.foreground) {
|
|
187
|
+
startForeground(config);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Default: Daemon mode
|
|
192
|
+
console.log(chalk.bold('Starting NexusCLI...'));
|
|
193
|
+
console.log('');
|
|
194
|
+
|
|
195
|
+
const pid = startDaemon(config);
|
|
196
|
+
|
|
197
|
+
console.log(chalk.green(` ✓ Server started (PID: ${pid})`));
|
|
198
|
+
console.log(chalk.gray(` Logs: ${PATHS.SERVER_LOG}`));
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log(chalk.bold(` ${chalk.cyan(url)}`));
|
|
201
|
+
console.log('');
|
|
202
|
+
|
|
203
|
+
// Ask to open browser (unless --no-browser)
|
|
204
|
+
if (options.browser !== false) {
|
|
205
|
+
const shouldOpen = await askYesNo(` Open browser? ${chalk.gray('(Y/n)')} `);
|
|
206
|
+
|
|
207
|
+
if (shouldOpen) {
|
|
208
|
+
const opened = openBrowser(url);
|
|
209
|
+
if (opened) {
|
|
210
|
+
console.log(chalk.green(' ✓ Browser opened'));
|
|
211
|
+
} else {
|
|
212
|
+
console.log(chalk.yellow(' Could not open browser'));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log('');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = start;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nexuscli status - Show server and engine status
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const { isInitialized, getConfig } = require('../config/manager');
|
|
11
|
+
const { PATHS, HOME } = require('../utils/paths');
|
|
12
|
+
const { isTermux, isTermuxApiWorking } = require('../utils/termux');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if server is running
|
|
16
|
+
*/
|
|
17
|
+
function getServerStatus() {
|
|
18
|
+
if (!fs.existsSync(PATHS.PID_FILE)) {
|
|
19
|
+
return { running: false };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const pid = parseInt(fs.readFileSync(PATHS.PID_FILE, 'utf8').trim());
|
|
24
|
+
process.kill(pid, 0);
|
|
25
|
+
|
|
26
|
+
// Try to get uptime from /proc
|
|
27
|
+
let uptime = null;
|
|
28
|
+
try {
|
|
29
|
+
const stat = fs.statSync(`/proc/${pid}`);
|
|
30
|
+
const uptimeMs = Date.now() - stat.ctimeMs;
|
|
31
|
+
uptime = formatUptime(uptimeMs);
|
|
32
|
+
} catch {}
|
|
33
|
+
|
|
34
|
+
return { running: true, pid, uptime };
|
|
35
|
+
} catch {
|
|
36
|
+
return { running: false };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Format uptime in human readable format
|
|
42
|
+
*/
|
|
43
|
+
function formatUptime(ms) {
|
|
44
|
+
const seconds = Math.floor(ms / 1000);
|
|
45
|
+
const minutes = Math.floor(seconds / 60);
|
|
46
|
+
const hours = Math.floor(minutes / 60);
|
|
47
|
+
const days = Math.floor(hours / 24);
|
|
48
|
+
|
|
49
|
+
if (days > 0) return `${days}d ${hours % 24}h`;
|
|
50
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
51
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
52
|
+
return `${seconds}s`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check CLI status for an engine
|
|
57
|
+
*/
|
|
58
|
+
function getEngineStatus(engine) {
|
|
59
|
+
try {
|
|
60
|
+
const output = execSync(`${engine} --version 2>/dev/null`, { encoding: 'utf8' });
|
|
61
|
+
const version = output.trim().split('\n')[0];
|
|
62
|
+
return { available: true, version };
|
|
63
|
+
} catch {
|
|
64
|
+
return { available: false };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Count workspaces with sessions
|
|
70
|
+
*/
|
|
71
|
+
function countWorkspaces() {
|
|
72
|
+
const projectsDir = path.join(HOME, '.claude', 'projects');
|
|
73
|
+
if (!fs.existsSync(projectsDir)) {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const entries = fs.readdirSync(projectsDir, { withFileTypes: true });
|
|
79
|
+
return entries.filter(e => e.isDirectory()).length;
|
|
80
|
+
} catch {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Main status command (TRI CLI v0.4.0)
|
|
87
|
+
*/
|
|
88
|
+
async function status() {
|
|
89
|
+
console.log('');
|
|
90
|
+
|
|
91
|
+
if (!isInitialized()) {
|
|
92
|
+
console.log(chalk.yellow('NexusCLI is not configured.'));
|
|
93
|
+
console.log(`Run ${chalk.cyan('nexuscli init')} first.`);
|
|
94
|
+
console.log('');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const config = getConfig();
|
|
99
|
+
const serverStatus = getServerStatus();
|
|
100
|
+
const claudeStatus = getEngineStatus('claude');
|
|
101
|
+
const codexStatus = getEngineStatus('codex');
|
|
102
|
+
const geminiStatus = getEngineStatus('gemini');
|
|
103
|
+
const workspaceCount = countWorkspaces();
|
|
104
|
+
|
|
105
|
+
// Header
|
|
106
|
+
console.log(chalk.bold('╔═══════════════════════════════════════════╗'));
|
|
107
|
+
console.log(chalk.bold('║ NexusCLI Status (TRI CLI v0.4.0) ║'));
|
|
108
|
+
console.log(chalk.bold('╠═══════════════════════════════════════════╣'));
|
|
109
|
+
|
|
110
|
+
// Server
|
|
111
|
+
if (serverStatus.running) {
|
|
112
|
+
const uptimeStr = serverStatus.uptime ? ` (${serverStatus.uptime})` : '';
|
|
113
|
+
console.log(chalk.bold(`║ Server: ${chalk.green('✓ Running')} ${chalk.gray(`PID ${serverStatus.pid}`)}${uptimeStr}`));
|
|
114
|
+
} else {
|
|
115
|
+
console.log(chalk.bold(`║ Server: ${chalk.red('✗ Stopped')}`));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(chalk.bold(`║ Port: ${chalk.white(config.server.port)}`));
|
|
119
|
+
console.log(chalk.bold('║'));
|
|
120
|
+
|
|
121
|
+
// Engines
|
|
122
|
+
console.log(chalk.bold('║ Engines:'));
|
|
123
|
+
|
|
124
|
+
// Claude
|
|
125
|
+
if (claudeStatus.available) {
|
|
126
|
+
const model = config.engines?.claude?.model || 'sonnet';
|
|
127
|
+
console.log(chalk.bold(`║ Claude: ${chalk.green('✓')} ${chalk.gray(model)}`));
|
|
128
|
+
} else {
|
|
129
|
+
console.log(chalk.bold(`║ Claude: ${chalk.red('✗')} ${chalk.gray('not found')}`));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Codex
|
|
133
|
+
if (codexStatus.available) {
|
|
134
|
+
const enabled = config.engines?.codex?.enabled;
|
|
135
|
+
const model = config.engines?.codex?.model || 'gpt-5.1-codex-max';
|
|
136
|
+
if (enabled) {
|
|
137
|
+
console.log(chalk.bold(`║ Codex: ${chalk.green('✓')} ${chalk.gray(model)}`));
|
|
138
|
+
} else {
|
|
139
|
+
console.log(chalk.bold(`║ Codex: ${chalk.gray('○')} ${chalk.gray('disabled')}`));
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.bold(`║ Codex: ${chalk.gray('○')} ${chalk.gray('not found')}`));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Gemini
|
|
146
|
+
if (geminiStatus.available) {
|
|
147
|
+
const enabled = config.engines?.gemini?.enabled;
|
|
148
|
+
const model = config.engines?.gemini?.model || 'gemini-3-pro-preview';
|
|
149
|
+
if (enabled) {
|
|
150
|
+
console.log(chalk.bold(`║ Gemini: ${chalk.green('✓')} ${chalk.gray(model)}`));
|
|
151
|
+
} else {
|
|
152
|
+
console.log(chalk.bold(`║ Gemini: ${chalk.gray('○')} ${chalk.gray('disabled')}`));
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
console.log(chalk.bold(`║ Gemini: ${chalk.gray('○')} ${chalk.gray('not found')}`));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(chalk.bold('║'));
|
|
159
|
+
|
|
160
|
+
// Workspaces
|
|
161
|
+
console.log(chalk.bold(`║ Workspaces: ${chalk.white(workspaceCount)} indexed`));
|
|
162
|
+
|
|
163
|
+
// Termux status
|
|
164
|
+
if (isTermux()) {
|
|
165
|
+
console.log(chalk.bold('║'));
|
|
166
|
+
console.log(chalk.bold('║ Termux:'));
|
|
167
|
+
console.log(chalk.bold(`║ Wake-lock: ${config.termux?.wake_lock ? chalk.green('enabled') : chalk.gray('disabled')}`));
|
|
168
|
+
console.log(chalk.bold(`║ Boot-start: ${config.termux?.boot_start ? chalk.green('enabled') : chalk.gray('disabled')}`));
|
|
169
|
+
|
|
170
|
+
if (isTermuxApiWorking()) {
|
|
171
|
+
console.log(chalk.bold(`║ API app: ${chalk.green('working')}`));
|
|
172
|
+
} else {
|
|
173
|
+
console.log(chalk.bold(`║ API app: ${chalk.yellow('not detected')}`));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(chalk.bold('╚═══════════════════════════════════════════╝'));
|
|
178
|
+
console.log('');
|
|
179
|
+
|
|
180
|
+
// URL if running
|
|
181
|
+
if (serverStatus.running) {
|
|
182
|
+
console.log(chalk.cyan(` http://localhost:${config.server.port}`));
|
|
183
|
+
console.log('');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = status;
|
package/lib/cli/stop.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nexuscli stop - Stop the daemon
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
const { PATHS } = require('../utils/paths');
|
|
9
|
+
const { isTermux, releaseWakeLock, sendNotification } = require('../utils/termux');
|
|
10
|
+
const { getConfig } = require('../config/manager');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Main stop command
|
|
14
|
+
*/
|
|
15
|
+
async function stop() {
|
|
16
|
+
console.log('');
|
|
17
|
+
|
|
18
|
+
// Check PID file
|
|
19
|
+
if (!fs.existsSync(PATHS.PID_FILE)) {
|
|
20
|
+
console.log(chalk.yellow('No running daemon found.'));
|
|
21
|
+
console.log('');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const pid = parseInt(fs.readFileSync(PATHS.PID_FILE, 'utf8').trim());
|
|
27
|
+
|
|
28
|
+
// Try to kill the process
|
|
29
|
+
try {
|
|
30
|
+
process.kill(pid, 'SIGTERM');
|
|
31
|
+
console.log(chalk.green(` ✓ Server stopped (PID: ${pid})`));
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (err.code === 'ESRCH') {
|
|
34
|
+
console.log(chalk.yellow(' Process not found (may have already stopped)'));
|
|
35
|
+
} else {
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Remove PID file
|
|
41
|
+
fs.unlinkSync(PATHS.PID_FILE);
|
|
42
|
+
|
|
43
|
+
// Release wake lock on Termux
|
|
44
|
+
const config = getConfig();
|
|
45
|
+
if (isTermux() && config.termux?.wake_lock) {
|
|
46
|
+
releaseWakeLock();
|
|
47
|
+
console.log(chalk.gray(' Wake-lock released'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Send notification on Termux
|
|
51
|
+
if (isTermux() && config.termux?.notifications) {
|
|
52
|
+
sendNotification('NexusCLI', 'Server stopped');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('');
|
|
56
|
+
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.log(chalk.red(` ✗ Error stopping server: ${err.message}`));
|
|
59
|
+
console.log('');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = stop;
|