@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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NexusCLI - Uninstall Command
|
|
3
|
+
* Prepares for clean uninstallation with optional data removal
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
const DATA_DIR = path.join(process.env.HOME, '.nexuscli');
|
|
13
|
+
const BOOT_SCRIPT = path.join(process.env.HOME, '.termux/boot/nexuscli-boot.sh');
|
|
14
|
+
|
|
15
|
+
function prompt(question) {
|
|
16
|
+
const rl = readline.createInterface({
|
|
17
|
+
input: process.stdin,
|
|
18
|
+
output: process.stdout
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
rl.question(question, (answer) => {
|
|
23
|
+
rl.close();
|
|
24
|
+
resolve(answer.toLowerCase().trim());
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatSize(bytes) {
|
|
30
|
+
if (bytes < 1024) return bytes + ' B';
|
|
31
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
32
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getDirSize(dirPath) {
|
|
36
|
+
let totalSize = 0;
|
|
37
|
+
try {
|
|
38
|
+
const files = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const filePath = path.join(dirPath, file.name);
|
|
41
|
+
if (file.isDirectory()) {
|
|
42
|
+
totalSize += getDirSize(filePath);
|
|
43
|
+
} else {
|
|
44
|
+
totalSize += fs.statSync(filePath).size;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Ignore errors
|
|
49
|
+
}
|
|
50
|
+
return totalSize;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function uninstallCommand(options) {
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(chalk.red('╔══════════════════════════════════════════════╗'));
|
|
56
|
+
console.log(chalk.red('║') + chalk.white.bold(' 🗑️ NexusCLI Uninstall ') + chalk.red('║'));
|
|
57
|
+
console.log(chalk.red('╚══════════════════════════════════════════════╝'));
|
|
58
|
+
console.log('');
|
|
59
|
+
|
|
60
|
+
// Check if data directory exists
|
|
61
|
+
const dataExists = fs.existsSync(DATA_DIR);
|
|
62
|
+
const bootExists = fs.existsSync(BOOT_SCRIPT);
|
|
63
|
+
|
|
64
|
+
if (!dataExists && !bootExists) {
|
|
65
|
+
console.log(chalk.yellow('Nessun dato NexusCLI trovato.'));
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log('Per rimuovere il pacchetto npm, esegui:');
|
|
68
|
+
console.log(chalk.cyan(' npm uninstall -g @mmmbuto/nexuscli'));
|
|
69
|
+
console.log('');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Show what will be affected
|
|
74
|
+
console.log(chalk.white.bold('Dati trovati:'));
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
if (dataExists) {
|
|
78
|
+
const dataSize = getDirSize(DATA_DIR);
|
|
79
|
+
console.log(chalk.cyan(` 📁 ${DATA_DIR}`));
|
|
80
|
+
console.log(chalk.gray(` Dimensione: ${formatSize(dataSize)}`));
|
|
81
|
+
|
|
82
|
+
// List contents
|
|
83
|
+
const contents = fs.readdirSync(DATA_DIR);
|
|
84
|
+
for (const item of contents) {
|
|
85
|
+
const itemPath = path.join(DATA_DIR, item);
|
|
86
|
+
const stat = fs.statSync(itemPath);
|
|
87
|
+
const icon = stat.isDirectory() ? '📂' : '📄';
|
|
88
|
+
const size = stat.isDirectory() ? formatSize(getDirSize(itemPath)) : formatSize(stat.size);
|
|
89
|
+
console.log(chalk.gray(` ${icon} ${item} (${size})`));
|
|
90
|
+
}
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (bootExists) {
|
|
95
|
+
console.log(chalk.cyan(` 🚀 ${BOOT_SCRIPT}`));
|
|
96
|
+
console.log(chalk.gray(' Script avvio automatico Termux'));
|
|
97
|
+
console.log('');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Stop server if running
|
|
101
|
+
console.log(chalk.yellow('Arresto server in corso...'));
|
|
102
|
+
try {
|
|
103
|
+
execSync('pkill -f "node.*nexuscli" 2>/dev/null || true', { stdio: 'ignore' });
|
|
104
|
+
console.log(chalk.green('✓ Server arrestato'));
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.log(chalk.gray(' Server non in esecuzione'));
|
|
107
|
+
}
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
// Ask what to do
|
|
111
|
+
console.log(chalk.white.bold('Cosa vuoi fare?'));
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(chalk.cyan(' 1)') + ' Rimuovi TUTTO (dati + configurazione + boot script)');
|
|
114
|
+
console.log(chalk.cyan(' 2)') + ' Mantieni i dati (solo rimuovi boot script)');
|
|
115
|
+
console.log(chalk.cyan(' 3)') + ' Annulla');
|
|
116
|
+
console.log('');
|
|
117
|
+
|
|
118
|
+
const choice = await prompt(chalk.yellow('Scelta [1/2/3]: '));
|
|
119
|
+
|
|
120
|
+
if (choice === '3' || choice === 'n' || choice === 'no' || choice === '') {
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(chalk.green('Operazione annullata.'));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (choice === '1') {
|
|
127
|
+
// Remove everything
|
|
128
|
+
console.log('');
|
|
129
|
+
const confirm = await prompt(chalk.red.bold('⚠️ ATTENZIONE: Tutti i dati saranno eliminati! Confermi? [s/N]: '));
|
|
130
|
+
|
|
131
|
+
if (confirm !== 's' && confirm !== 'si' && confirm !== 'y' && confirm !== 'yes') {
|
|
132
|
+
console.log('');
|
|
133
|
+
console.log(chalk.green('Operazione annullata.'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(chalk.yellow('Rimozione in corso...'));
|
|
139
|
+
|
|
140
|
+
// Remove boot script
|
|
141
|
+
if (bootExists) {
|
|
142
|
+
try {
|
|
143
|
+
fs.unlinkSync(BOOT_SCRIPT);
|
|
144
|
+
console.log(chalk.green(`✓ Rimosso ${BOOT_SCRIPT}`));
|
|
145
|
+
} catch (e) {
|
|
146
|
+
console.log(chalk.red(`✗ Errore rimozione boot script: ${e.message}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Remove data directory
|
|
151
|
+
if (dataExists) {
|
|
152
|
+
try {
|
|
153
|
+
fs.rmSync(DATA_DIR, { recursive: true, force: true });
|
|
154
|
+
console.log(chalk.green(`✓ Rimosso ${DATA_DIR}`));
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.log(chalk.red(`✗ Errore rimozione dati: ${e.message}`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log(chalk.green.bold('✓ Pulizia completata!'));
|
|
162
|
+
|
|
163
|
+
} else if (choice === '2') {
|
|
164
|
+
// Keep data, only remove boot script
|
|
165
|
+
console.log('');
|
|
166
|
+
|
|
167
|
+
if (bootExists) {
|
|
168
|
+
try {
|
|
169
|
+
fs.unlinkSync(BOOT_SCRIPT);
|
|
170
|
+
console.log(chalk.green(`✓ Rimosso ${BOOT_SCRIPT}`));
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.log(chalk.red(`✗ Errore rimozione boot script: ${e.message}`));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log(chalk.green.bold('✓ Boot script rimosso'));
|
|
178
|
+
console.log(chalk.cyan(` I tuoi dati sono stati mantenuti in: ${DATA_DIR}`));
|
|
179
|
+
console.log(chalk.gray(' Reinstallando NexusCLI, ritroverai le tue configurazioni.'));
|
|
180
|
+
|
|
181
|
+
} else {
|
|
182
|
+
console.log('');
|
|
183
|
+
console.log(chalk.red('Scelta non valida. Operazione annullata.'));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log('');
|
|
188
|
+
console.log(chalk.white.bold('Per completare la disinstallazione, esegui:'));
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log(chalk.cyan(' npm uninstall -g @mmmbuto/nexuscli'));
|
|
191
|
+
console.log('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = uninstallCommand;
|
package/lib/cli/users.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nexuscli users - User management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const { isInitialized } = require('../config/manager');
|
|
8
|
+
const { initDb, prepare } = require('../server/db');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List all users
|
|
12
|
+
*/
|
|
13
|
+
async function listUsers() {
|
|
14
|
+
const { getConfig } = require('../config/manager');
|
|
15
|
+
const config = getConfig();
|
|
16
|
+
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log(chalk.bold('Users:'));
|
|
19
|
+
console.log('');
|
|
20
|
+
|
|
21
|
+
// Show config user (main admin from init)
|
|
22
|
+
if (config.auth && config.auth.user) {
|
|
23
|
+
console.log(` ${chalk.cyan(config.auth.user)} [${chalk.red('admin')}] ${chalk.gray('(config)')}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Also check database for additional users
|
|
27
|
+
try {
|
|
28
|
+
await initDb();
|
|
29
|
+
const dbUsers = prepare('SELECT id, username, role FROM users ORDER BY created_at').all();
|
|
30
|
+
|
|
31
|
+
for (const user of dbUsers) {
|
|
32
|
+
// Skip if same as config user
|
|
33
|
+
if (config.auth && user.username === config.auth.user) continue;
|
|
34
|
+
|
|
35
|
+
const role = user.role === 'admin' ? chalk.red('admin') : chalk.gray('user');
|
|
36
|
+
console.log(` ${chalk.cyan(user.username)} [${role}]`);
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// DB not available, just show config user
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!config.auth?.user) {
|
|
43
|
+
console.log(chalk.yellow('No users found.'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log('');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Add a new user
|
|
51
|
+
*/
|
|
52
|
+
async function addUser(options) {
|
|
53
|
+
await initDb();
|
|
54
|
+
const User = require('../server/models/User');
|
|
55
|
+
|
|
56
|
+
let username = options.username;
|
|
57
|
+
let password = options.password;
|
|
58
|
+
let role = options.admin ? 'admin' : 'user';
|
|
59
|
+
|
|
60
|
+
// Interactive mode if no username provided
|
|
61
|
+
if (!username) {
|
|
62
|
+
const answers = await inquirer.prompt([
|
|
63
|
+
{
|
|
64
|
+
type: 'input',
|
|
65
|
+
name: 'username',
|
|
66
|
+
message: 'Username:',
|
|
67
|
+
validate: (input) => input.length >= 3 || 'Username must be at least 3 characters'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'password',
|
|
71
|
+
name: 'password',
|
|
72
|
+
message: 'Password:',
|
|
73
|
+
mask: '*',
|
|
74
|
+
validate: (input) => input.length >= 4 || 'Password must be at least 4 characters'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'confirm',
|
|
78
|
+
name: 'isAdmin',
|
|
79
|
+
message: 'Admin user?',
|
|
80
|
+
default: false
|
|
81
|
+
}
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
username = answers.username;
|
|
85
|
+
password = answers.password;
|
|
86
|
+
role = answers.isAdmin ? 'admin' : 'user';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check if user exists
|
|
90
|
+
const existing = prepare('SELECT id FROM users WHERE username = ?').get(username);
|
|
91
|
+
if (existing) {
|
|
92
|
+
console.log(chalk.red(`User '${username}' already exists.`));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create user
|
|
97
|
+
try {
|
|
98
|
+
const user = User.create(username, password, role);
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(chalk.green(`✓ User '${user.username}' created (${user.role})`));
|
|
101
|
+
console.log('');
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.log(chalk.red(`Error creating user: ${error.message}`));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Change user password
|
|
110
|
+
*/
|
|
111
|
+
async function changePassword(options) {
|
|
112
|
+
await initDb();
|
|
113
|
+
const bcrypt = require('bcryptjs');
|
|
114
|
+
const { getDb, saveDb } = require('../server/db');
|
|
115
|
+
|
|
116
|
+
let username = options.username;
|
|
117
|
+
let newPassword;
|
|
118
|
+
|
|
119
|
+
// Get list of users for selection
|
|
120
|
+
const users = prepare('SELECT username FROM users ORDER BY username').all();
|
|
121
|
+
|
|
122
|
+
if (users.length === 0) {
|
|
123
|
+
console.log(chalk.yellow('No users found.'));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Interactive mode
|
|
128
|
+
if (!username) {
|
|
129
|
+
const answers = await inquirer.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'username',
|
|
133
|
+
message: 'Select user:',
|
|
134
|
+
choices: users.map(u => u.username)
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
type: 'password',
|
|
138
|
+
name: 'newPassword',
|
|
139
|
+
message: 'New password:',
|
|
140
|
+
mask: '*',
|
|
141
|
+
validate: (input) => input.length >= 4 || 'Password must be at least 4 characters'
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'password',
|
|
145
|
+
name: 'confirmPassword',
|
|
146
|
+
message: 'Confirm password:',
|
|
147
|
+
mask: '*'
|
|
148
|
+
}
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
if (answers.newPassword !== answers.confirmPassword) {
|
|
152
|
+
console.log(chalk.red('Passwords do not match.'));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
username = answers.username;
|
|
157
|
+
newPassword = answers.newPassword;
|
|
158
|
+
} else {
|
|
159
|
+
// Non-interactive: prompt only for password
|
|
160
|
+
const answers = await inquirer.prompt([
|
|
161
|
+
{
|
|
162
|
+
type: 'password',
|
|
163
|
+
name: 'newPassword',
|
|
164
|
+
message: 'New password:',
|
|
165
|
+
mask: '*',
|
|
166
|
+
validate: (input) => input.length >= 4 || 'Password must be at least 4 characters'
|
|
167
|
+
}
|
|
168
|
+
]);
|
|
169
|
+
newPassword = answers.newPassword;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Update password
|
|
173
|
+
const user = prepare('SELECT id FROM users WHERE username = ?').get(username);
|
|
174
|
+
if (!user) {
|
|
175
|
+
console.log(chalk.red(`User '${username}' not found.`));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const hash = bcrypt.hashSync(newPassword, 10);
|
|
180
|
+
const db = getDb();
|
|
181
|
+
db.run('UPDATE users SET password_hash = ? WHERE username = ?', [hash, username]);
|
|
182
|
+
saveDb();
|
|
183
|
+
|
|
184
|
+
console.log('');
|
|
185
|
+
console.log(chalk.green(`✓ Password changed for '${username}'`));
|
|
186
|
+
console.log('');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Delete a user
|
|
191
|
+
*/
|
|
192
|
+
async function deleteUser(options) {
|
|
193
|
+
await initDb();
|
|
194
|
+
const { getDb, saveDb } = require('../server/db');
|
|
195
|
+
|
|
196
|
+
let username = options.username;
|
|
197
|
+
|
|
198
|
+
// Get list of users
|
|
199
|
+
const users = prepare('SELECT username FROM users ORDER BY username').all();
|
|
200
|
+
|
|
201
|
+
if (users.length === 0) {
|
|
202
|
+
console.log(chalk.yellow('No users found.'));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (users.length === 1) {
|
|
207
|
+
console.log(chalk.yellow('Cannot delete the only user.'));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Interactive mode
|
|
212
|
+
if (!username) {
|
|
213
|
+
const answers = await inquirer.prompt([
|
|
214
|
+
{
|
|
215
|
+
type: 'list',
|
|
216
|
+
name: 'username',
|
|
217
|
+
message: 'Select user to delete:',
|
|
218
|
+
choices: users.map(u => u.username)
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
type: 'confirm',
|
|
222
|
+
name: 'confirm',
|
|
223
|
+
message: 'Are you sure?',
|
|
224
|
+
default: false
|
|
225
|
+
}
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
if (!answers.confirm) {
|
|
229
|
+
console.log(chalk.gray('Cancelled.'));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
username = answers.username;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Delete user
|
|
237
|
+
const db = getDb();
|
|
238
|
+
const result = db.run('DELETE FROM users WHERE username = ?', [username]);
|
|
239
|
+
saveDb();
|
|
240
|
+
|
|
241
|
+
if (result.changes > 0) {
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log(chalk.green(`✓ User '${username}' deleted`));
|
|
244
|
+
console.log('');
|
|
245
|
+
} else {
|
|
246
|
+
console.log(chalk.red(`User '${username}' not found.`));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Main users command
|
|
252
|
+
*/
|
|
253
|
+
async function users(subcommand, options = {}) {
|
|
254
|
+
console.log('');
|
|
255
|
+
|
|
256
|
+
// Check if initialized
|
|
257
|
+
if (!isInitialized()) {
|
|
258
|
+
console.log(chalk.yellow('NexusCLI is not configured.'));
|
|
259
|
+
console.log(`Run ${chalk.cyan('nexuscli init')} first.`);
|
|
260
|
+
console.log('');
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
switch (subcommand) {
|
|
265
|
+
case 'list':
|
|
266
|
+
case 'ls':
|
|
267
|
+
await listUsers();
|
|
268
|
+
break;
|
|
269
|
+
case 'add':
|
|
270
|
+
case 'create':
|
|
271
|
+
await addUser(options);
|
|
272
|
+
break;
|
|
273
|
+
case 'passwd':
|
|
274
|
+
case 'password':
|
|
275
|
+
await changePassword(options);
|
|
276
|
+
break;
|
|
277
|
+
case 'delete':
|
|
278
|
+
case 'del':
|
|
279
|
+
case 'rm':
|
|
280
|
+
await deleteUser(options);
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
// Show help
|
|
284
|
+
console.log(chalk.bold('User Management'));
|
|
285
|
+
console.log('');
|
|
286
|
+
console.log('Commands:');
|
|
287
|
+
console.log(` ${chalk.cyan('nexuscli users list')} List all users`);
|
|
288
|
+
console.log(` ${chalk.cyan('nexuscli users add')} Add a new user`);
|
|
289
|
+
console.log(` ${chalk.cyan('nexuscli users passwd')} Change password`);
|
|
290
|
+
console.log(` ${chalk.cyan('nexuscli users delete')} Delete a user`);
|
|
291
|
+
console.log('');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = users;
|