@mmmbuto/nexuscrew 0.1.0-beta.1 → 0.2.1
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 +67 -120
- package/bin/nexuscrew.js +468 -264
- package/frontend/dist/apple-touch-icon.png +0 -0
- package/frontend/dist/assets/{index-C7bAndew.js → index-BG1N7bSL.js} +1710 -1702
- package/frontend/dist/assets/index-CiAtinNP.css +1 -0
- package/frontend/dist/favicon.svg +20 -0
- package/frontend/dist/icon-192.png +0 -0
- package/frontend/dist/icon-512.png +0 -0
- package/frontend/dist/index.html +11 -3
- package/frontend/dist/site.webmanifest +29 -0
- package/lib/config/hosts.js +67 -0
- package/lib/config/manager.js +379 -0
- package/lib/config/models.js +408 -0
- package/lib/server/db/adapter.js +274 -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_base_schema.sql +70 -0
- package/lib/server/middleware/auth.js +134 -0
- package/lib/server/middleware/rate-limit.js +63 -0
- package/lib/server/models/User.js +128 -0
- package/lib/server/routes/auth.js +168 -0
- package/lib/server/routes/hosts.js +11 -20
- package/lib/server/routes/keys.js +28 -0
- package/lib/server/routes/models.js +59 -4
- package/lib/server/routes/runtimes.js +34 -0
- package/lib/server/routes/send.js +76 -12
- package/lib/server/routes/sessions.js +39 -10
- package/lib/server/routes/speech.js +46 -0
- package/lib/server/routes/status.js +8 -6
- package/lib/server/routes/upload.js +135 -0
- package/lib/server/routes/wake-lock.js +95 -0
- package/lib/server/routes/workspaces.js +101 -0
- package/lib/server/server.js +66 -33
- package/lib/server/services/attachment-manager.js +57 -0
- package/lib/server/services/context-bridge.js +425 -0
- package/lib/server/services/runtime-manager.js +462 -0
- package/lib/server/services/speech-manager.js +76 -0
- package/lib/server/services/summary-generator.js +309 -0
- package/lib/server/services/workspace-manager.js +79 -0
- package/lib/services/engine-discovery.js +198 -13
- package/lib/services/log-watcher.js +40 -22
- package/lib/services/remote-pane-watcher.js +155 -0
- package/lib/services/session-store.js +60 -64
- package/lib/services/tmux-manager.js +127 -116
- package/lib/setup/postinstall.js +38 -0
- package/lib/utils/paths.js +124 -0
- package/lib/utils/termux.js +182 -0
- package/package.json +8 -4
- package/frontend/dist/assets/index-OENqI1_9.css +0 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager
|
|
3
|
+
* Handles reading/writing ~/.nexuscrew/config.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { PATHS, ensureDirectories } = require('../utils/paths');
|
|
9
|
+
const { DEFAULT_HOSTS, writeHosts } = require('./hosts');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default configuration
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_CONFIG = {
|
|
15
|
+
version: 3,
|
|
16
|
+
server: {
|
|
17
|
+
port: 41820,
|
|
18
|
+
host: '0.0.0.0'
|
|
19
|
+
},
|
|
20
|
+
auth: {
|
|
21
|
+
jwt_secret: null, // Generated on init
|
|
22
|
+
user: 'admin',
|
|
23
|
+
pass_hash: null // Generated on init
|
|
24
|
+
},
|
|
25
|
+
tmuxSession: 'nexuscrew',
|
|
26
|
+
logDir: PATHS.LOGS_DIR,
|
|
27
|
+
providersPath: '',
|
|
28
|
+
hosts: {
|
|
29
|
+
default: 'local'
|
|
30
|
+
},
|
|
31
|
+
engines: {
|
|
32
|
+
active: ['claude'],
|
|
33
|
+
check_on_boot: false,
|
|
34
|
+
claude: {
|
|
35
|
+
enabled: true,
|
|
36
|
+
path: 'auto',
|
|
37
|
+
model: 'sonnet',
|
|
38
|
+
lanes: {
|
|
39
|
+
native: {
|
|
40
|
+
runtimeId: 'claude-native',
|
|
41
|
+
command: 'claude',
|
|
42
|
+
enabled: true
|
|
43
|
+
},
|
|
44
|
+
custom: {
|
|
45
|
+
runtimeId: 'claude-custom',
|
|
46
|
+
command: 'claude',
|
|
47
|
+
enabled: true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
codex: {
|
|
52
|
+
enabled: false,
|
|
53
|
+
path: null,
|
|
54
|
+
model: 'gpt-5.3-codex',
|
|
55
|
+
lanes: {
|
|
56
|
+
native: {
|
|
57
|
+
runtimeId: 'codex-native',
|
|
58
|
+
command: 'codex',
|
|
59
|
+
enabled: true
|
|
60
|
+
},
|
|
61
|
+
custom: {
|
|
62
|
+
runtimeId: 'codex-custom',
|
|
63
|
+
command: 'codex-lts',
|
|
64
|
+
enabled: true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
gemini: {
|
|
69
|
+
enabled: false,
|
|
70
|
+
path: null,
|
|
71
|
+
model: 'gemini-3-pro-preview',
|
|
72
|
+
lanes: {
|
|
73
|
+
native: {
|
|
74
|
+
runtimeId: 'gemini-native',
|
|
75
|
+
command: 'gemini',
|
|
76
|
+
enabled: true
|
|
77
|
+
},
|
|
78
|
+
custom: {
|
|
79
|
+
runtimeId: 'gemini-custom',
|
|
80
|
+
command: 'gemini',
|
|
81
|
+
enabled: false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
qwen: {
|
|
86
|
+
enabled: false,
|
|
87
|
+
path: null,
|
|
88
|
+
model: 'qwen3-coder-plus',
|
|
89
|
+
lanes: {
|
|
90
|
+
native: {
|
|
91
|
+
runtimeId: 'qwen-native',
|
|
92
|
+
command: 'qwen',
|
|
93
|
+
enabled: true
|
|
94
|
+
},
|
|
95
|
+
custom: {
|
|
96
|
+
runtimeId: 'qwen-custom',
|
|
97
|
+
command: 'qwen',
|
|
98
|
+
enabled: false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
runtimes: {
|
|
104
|
+
preferredInstallMethod: 'npm',
|
|
105
|
+
customProviders: {}
|
|
106
|
+
},
|
|
107
|
+
workspaces: {
|
|
108
|
+
default: null,
|
|
109
|
+
paths: []
|
|
110
|
+
},
|
|
111
|
+
termux: {
|
|
112
|
+
wake_lock: true,
|
|
113
|
+
notifications: true,
|
|
114
|
+
boot_start: false,
|
|
115
|
+
service_enabled: false,
|
|
116
|
+
service_name: 'nexuscrew'
|
|
117
|
+
},
|
|
118
|
+
stt: {
|
|
119
|
+
provider: 'whispercpp',
|
|
120
|
+
fallback: 'browser',
|
|
121
|
+
whisper: {
|
|
122
|
+
bin: '',
|
|
123
|
+
model: '',
|
|
124
|
+
language: 'it',
|
|
125
|
+
threads: 2
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
tts: {
|
|
129
|
+
provider: 'system'
|
|
130
|
+
},
|
|
131
|
+
preferences: {
|
|
132
|
+
defaultModel: null
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
function isPlainObject(value) {
|
|
137
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function deepMergeDefaults(defaults, current) {
|
|
141
|
+
if (Array.isArray(defaults)) {
|
|
142
|
+
return Array.isArray(current) ? current : [...defaults];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!isPlainObject(defaults)) {
|
|
146
|
+
return current === undefined ? defaults : current;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const merged = {};
|
|
150
|
+
const keys = new Set([
|
|
151
|
+
...Object.keys(defaults),
|
|
152
|
+
...Object.keys(isPlainObject(current) ? current : {})
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
for (const key of keys) {
|
|
156
|
+
const defaultValue = defaults[key];
|
|
157
|
+
const currentValue = isPlainObject(current) ? current[key] : undefined;
|
|
158
|
+
|
|
159
|
+
if (defaultValue === undefined) {
|
|
160
|
+
merged[key] = currentValue;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
merged[key] = deepMergeDefaults(defaultValue, currentValue);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return merged;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizeConfig(config) {
|
|
171
|
+
const merged = deepMergeDefaults(DEFAULT_CONFIG, config || {});
|
|
172
|
+
merged.version = DEFAULT_CONFIG.version;
|
|
173
|
+
|
|
174
|
+
if (!Array.isArray(merged.engines.active) || merged.engines.active.length === 0) {
|
|
175
|
+
merged.engines.active = ['claude'];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return merged;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Load configuration from file
|
|
183
|
+
*/
|
|
184
|
+
function loadConfig() {
|
|
185
|
+
ensureDirectories();
|
|
186
|
+
|
|
187
|
+
if (!fs.existsSync(PATHS.CONFIG_FILE)) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const content = fs.readFileSync(PATHS.CONFIG_FILE, 'utf8');
|
|
193
|
+
return normalizeConfig(JSON.parse(content));
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('Error loading config:', error.message);
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Save configuration to file
|
|
202
|
+
*/
|
|
203
|
+
function saveConfig(config) {
|
|
204
|
+
ensureDirectories();
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
fs.writeFileSync(
|
|
208
|
+
PATHS.CONFIG_FILE,
|
|
209
|
+
JSON.stringify(config, null, 2),
|
|
210
|
+
'utf8'
|
|
211
|
+
);
|
|
212
|
+
return true;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('Error saving config:', error.message);
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get configuration (load or create default)
|
|
221
|
+
*/
|
|
222
|
+
function getConfig() {
|
|
223
|
+
const config = loadConfig();
|
|
224
|
+
if (config) {
|
|
225
|
+
return config;
|
|
226
|
+
}
|
|
227
|
+
return normalizeConfig(DEFAULT_CONFIG);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if NexusCrew is initialized
|
|
232
|
+
*/
|
|
233
|
+
function isInitialized() {
|
|
234
|
+
return fs.existsSync(PATHS.CONFIG_FILE);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get a config value by dot-notation path
|
|
239
|
+
*/
|
|
240
|
+
function getConfigValue(key) {
|
|
241
|
+
const config = getConfig();
|
|
242
|
+
const parts = key.split('.');
|
|
243
|
+
let value = config;
|
|
244
|
+
|
|
245
|
+
for (const part of parts) {
|
|
246
|
+
if (value === undefined || value === null) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
value = value[part];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Set a config value by dot-notation path
|
|
257
|
+
*/
|
|
258
|
+
function setConfigValue(key, value) {
|
|
259
|
+
const config = getConfig();
|
|
260
|
+
const parts = key.split('.');
|
|
261
|
+
let current = config;
|
|
262
|
+
|
|
263
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
264
|
+
const part = parts[i];
|
|
265
|
+
if (current[part] === undefined) {
|
|
266
|
+
current[part] = {};
|
|
267
|
+
}
|
|
268
|
+
current = current[part];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let parsedValue = value;
|
|
272
|
+
if (value === 'true') parsedValue = true;
|
|
273
|
+
else if (value === 'false') parsedValue = false;
|
|
274
|
+
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
|
|
275
|
+
|
|
276
|
+
current[parts[parts.length - 1]] = parsedValue;
|
|
277
|
+
|
|
278
|
+
return saveConfig(config);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get all config keys as flat list
|
|
283
|
+
*/
|
|
284
|
+
function getConfigKeys(obj = null, prefix = '') {
|
|
285
|
+
if (!obj) {
|
|
286
|
+
obj = getConfig();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const keys = [];
|
|
290
|
+
|
|
291
|
+
for (const key of Object.keys(obj)) {
|
|
292
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
293
|
+
|
|
294
|
+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
295
|
+
keys.push(...getConfigKeys(obj[key], fullKey));
|
|
296
|
+
} else {
|
|
297
|
+
keys.push(fullKey);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return keys;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Initialize config with generated secrets
|
|
306
|
+
* IMPORTANT: Creates ~/.nexuscrew/config.json and ~/.nexuscrew/hosts.json
|
|
307
|
+
* Does NOT create database user - that's handled by server.js seed
|
|
308
|
+
*/
|
|
309
|
+
function initializeConfig(options = {}) {
|
|
310
|
+
const { v4: uuidv4 } = require('uuid');
|
|
311
|
+
const bcrypt = require('bcryptjs');
|
|
312
|
+
|
|
313
|
+
const config = normalizeConfig(DEFAULT_CONFIG);
|
|
314
|
+
|
|
315
|
+
// Generate JWT secret
|
|
316
|
+
config.auth.jwt_secret = uuidv4() + '-' + uuidv4();
|
|
317
|
+
|
|
318
|
+
// Set user credentials
|
|
319
|
+
const username = options.username || 'admin';
|
|
320
|
+
const password = options.password || 'admin';
|
|
321
|
+
|
|
322
|
+
config.auth.user = username;
|
|
323
|
+
config.auth.pass_hash = bcrypt.hashSync(password, 10);
|
|
324
|
+
|
|
325
|
+
// Set workspaces
|
|
326
|
+
if (options.workspaces) {
|
|
327
|
+
config.workspaces.paths = options.workspaces;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Set default workspace
|
|
331
|
+
if (options.defaultWorkspace) {
|
|
332
|
+
config.workspaces.default = options.defaultWorkspace;
|
|
333
|
+
} else if (options.workspaces && options.workspaces.length > 0) {
|
|
334
|
+
config.workspaces.default = options.workspaces[0];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Set port
|
|
338
|
+
if (options.port) {
|
|
339
|
+
config.server.port = options.port;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Termux options
|
|
343
|
+
if (options.wakeLock !== undefined) {
|
|
344
|
+
config.termux.wake_lock = options.wakeLock;
|
|
345
|
+
}
|
|
346
|
+
if (options.notifications !== undefined) {
|
|
347
|
+
config.termux.notifications = options.notifications;
|
|
348
|
+
}
|
|
349
|
+
if (options.bootStart !== undefined) {
|
|
350
|
+
config.termux.boot_start = options.bootStart;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Save config
|
|
354
|
+
if (!saveConfig(config)) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Create default hosts.json
|
|
359
|
+
try {
|
|
360
|
+
writeHosts(DEFAULT_HOSTS);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('Error creating hosts.json:', error.message);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return config;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = {
|
|
369
|
+
DEFAULT_CONFIG,
|
|
370
|
+
normalizeConfig,
|
|
371
|
+
loadConfig,
|
|
372
|
+
saveConfig,
|
|
373
|
+
getConfig,
|
|
374
|
+
isInitialized,
|
|
375
|
+
getConfigValue,
|
|
376
|
+
setConfigValue,
|
|
377
|
+
getConfigKeys,
|
|
378
|
+
initializeConfig
|
|
379
|
+
};
|