@mmmbuto/nexuscrew 0.2.1 → 0.2.3

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.
Files changed (52) hide show
  1. package/README.md +122 -67
  2. package/bin/nexuscrew.js +194 -470
  3. package/frontend/dist/assets/{index-BG1N7bSL.js → index-BsldfYnr.js} +1704 -1711
  4. package/frontend/dist/assets/index-DcQGg3dp.css +1 -0
  5. package/frontend/dist/index.html +3 -11
  6. package/lib/server/routes/hosts.js +22 -12
  7. package/lib/server/routes/launchers.js +12 -0
  8. package/lib/server/routes/models.js +16 -63
  9. package/lib/server/routes/runtime.js +10 -0
  10. package/lib/server/routes/send.js +97 -258
  11. package/lib/server/routes/sessions.js +13 -42
  12. package/lib/server/routes/status.js +18 -22
  13. package/lib/server/routes/tmux.js +88 -0
  14. package/lib/server/server.js +60 -81
  15. package/lib/services/engine-discovery.js +192 -445
  16. package/lib/services/log-watcher.js +22 -40
  17. package/lib/services/session-store.js +88 -62
  18. package/lib/services/tmux-manager.js +111 -173
  19. package/package.json +6 -10
  20. package/frontend/dist/apple-touch-icon.png +0 -0
  21. package/frontend/dist/assets/index-CiAtinNP.css +0 -1
  22. package/frontend/dist/favicon.svg +0 -20
  23. package/frontend/dist/icon-192.png +0 -0
  24. package/frontend/dist/icon-512.png +0 -0
  25. package/frontend/dist/site.webmanifest +0 -29
  26. package/lib/config/hosts.js +0 -67
  27. package/lib/config/manager.js +0 -379
  28. package/lib/config/models.js +0 -408
  29. package/lib/server/db/adapter.js +0 -274
  30. package/lib/server/db/drivers/sql-js.js +0 -75
  31. package/lib/server/db/migrate.js +0 -174
  32. package/lib/server/db/migrations/001_base_schema.sql +0 -70
  33. package/lib/server/middleware/auth.js +0 -134
  34. package/lib/server/middleware/rate-limit.js +0 -63
  35. package/lib/server/models/User.js +0 -128
  36. package/lib/server/routes/auth.js +0 -168
  37. package/lib/server/routes/keys.js +0 -28
  38. package/lib/server/routes/runtimes.js +0 -34
  39. package/lib/server/routes/speech.js +0 -46
  40. package/lib/server/routes/upload.js +0 -135
  41. package/lib/server/routes/wake-lock.js +0 -95
  42. package/lib/server/routes/workspaces.js +0 -101
  43. package/lib/server/services/attachment-manager.js +0 -57
  44. package/lib/server/services/context-bridge.js +0 -425
  45. package/lib/server/services/runtime-manager.js +0 -462
  46. package/lib/server/services/speech-manager.js +0 -76
  47. package/lib/server/services/summary-generator.js +0 -309
  48. package/lib/server/services/workspace-manager.js +0 -79
  49. package/lib/services/remote-pane-watcher.js +0 -155
  50. package/lib/setup/postinstall.js +0 -38
  51. package/lib/utils/paths.js +0 -124
  52. package/lib/utils/termux.js +0 -182
@@ -1,29 +0,0 @@
1
- {
2
- "name": "NexusCrew",
3
- "short_name": "NexusCrew",
4
- "description": "tmux and SSH cockpit for Termux and mobile workflows",
5
- "start_url": "/",
6
- "display": "standalone",
7
- "background_color": "#07110d",
8
- "theme_color": "#07110d",
9
- "icons": [
10
- {
11
- "src": "/favicon.svg",
12
- "sizes": "any",
13
- "type": "image/svg+xml",
14
- "purpose": "any"
15
- },
16
- {
17
- "src": "/icon-192.png",
18
- "sizes": "192x192",
19
- "type": "image/png",
20
- "purpose": "any maskable"
21
- },
22
- {
23
- "src": "/icon-512.png",
24
- "sizes": "512x512",
25
- "type": "image/png",
26
- "purpose": "any maskable"
27
- }
28
- ]
29
- }
@@ -1,67 +0,0 @@
1
- const fs = require('fs');
2
- const { PATHS, ensureDirectories } = require('../utils/paths');
3
-
4
- const DEFAULT_HOSTS = [{ name: 'local', type: 'local', default: true }];
5
-
6
- function normalizeHost(host = {}) {
7
- return {
8
- name: host.name,
9
- type: host.type || (host.name === 'local' ? 'local' : 'ssh'),
10
- host: host.host || null,
11
- user: host.user || 'dag',
12
- port: String(host.port || '22'),
13
- key: host.key || null,
14
- default: Boolean(host.default)
15
- };
16
- }
17
-
18
- function readHosts() {
19
- ensureDirectories();
20
-
21
- if (!fs.existsSync(PATHS.HOSTS_FILE)) {
22
- return [...DEFAULT_HOSTS];
23
- }
24
-
25
- try {
26
- const parsed = JSON.parse(fs.readFileSync(PATHS.HOSTS_FILE, 'utf8'));
27
- if (!Array.isArray(parsed) || parsed.length === 0) {
28
- return [...DEFAULT_HOSTS];
29
- }
30
-
31
- const hosts = parsed.map(normalizeHost);
32
- if (!hosts.find((host) => host.name === 'local')) {
33
- hosts.unshift({ ...DEFAULT_HOSTS[0] });
34
- }
35
- return hosts;
36
- } catch (error) {
37
- console.error('[Hosts] Failed to read hosts registry:', error.message);
38
- return [...DEFAULT_HOSTS];
39
- }
40
- }
41
-
42
- function writeHosts(hosts = []) {
43
- ensureDirectories();
44
- const normalized = hosts.map(normalizeHost);
45
- fs.writeFileSync(PATHS.HOSTS_FILE, JSON.stringify(normalized, null, 2), 'utf8');
46
- return normalized;
47
- }
48
-
49
- function ensureHostsFile() {
50
- if (!fs.existsSync(PATHS.HOSTS_FILE)) {
51
- writeHosts(DEFAULT_HOSTS);
52
- }
53
- }
54
-
55
- function getHostByName(name) {
56
- if (!name || name === 'local') return { ...DEFAULT_HOSTS[0] };
57
- return readHosts().find((host) => host.name === name) || null;
58
- }
59
-
60
- module.exports = {
61
- DEFAULT_HOSTS,
62
- normalizeHost,
63
- readHosts,
64
- writeHosts,
65
- ensureHostsFile,
66
- getHostByName
67
- };
@@ -1,379 +0,0 @@
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
- };