@mmmbuto/nexuscrew 0.1.0-beta.1 → 0.2.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/README.md +54 -120
- package/bin/nexuscrew.js +9 -8
- package/frontend/dist/assets/{index-C7bAndew.js → index-8pw4eMB-.js} +1715 -1702
- package/frontend/dist/assets/index-BF0tdvNT.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/lib/config/manager.js +362 -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/keys.js +15 -0
- package/lib/server/routes/models.js +59 -4
- package/lib/server/routes/runtimes.js +34 -0
- package/lib/server/routes/send.js +28 -4
- package/lib/server/routes/sessions.js +34 -2
- package/lib/server/routes/speech.js +75 -0
- package/lib/server/routes/upload.js +134 -0
- package/lib/server/routes/wake-lock.js +95 -0
- package/lib/server/routes/workspaces.js +101 -0
- package/lib/server/server.js +48 -16
- package/lib/server/services/context-bridge.js +425 -0
- package/lib/server/services/runtime-manager.js +462 -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/session-store.js +57 -61
- package/lib/utils/paths.js +107 -0
- package/lib/utils/termux.js +145 -0
- package/package.json +6 -2
- package/frontend/dist/assets/index-OENqI1_9.css +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const router = express.Router();
|
|
4
|
+
|
|
5
|
+
// State to track wake lock status
|
|
6
|
+
let wakeLockAcquired = false;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* POST /api/v1/wake-lock
|
|
10
|
+
* Acquire wake lock (prevent Android from killing process)
|
|
11
|
+
* On non-Termux systems, this is a no-op but returns success to avoid errors
|
|
12
|
+
*/
|
|
13
|
+
router.post('/wake-lock', (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
console.log('[WakeLock] Acquiring wake lock...');
|
|
16
|
+
|
|
17
|
+
const isTermux = process.env.PREFIX?.includes('com.termux');
|
|
18
|
+
|
|
19
|
+
if (isTermux) {
|
|
20
|
+
// Only execute on Termux/Android
|
|
21
|
+
try {
|
|
22
|
+
execSync('termux-wake-lock', { stdio: 'ignore' });
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.warn('[WakeLock] termux-wake-lock command failed (may not be available)');
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
console.log('[WakeLock] Not on Termux - wake-lock is a no-op');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
wakeLockAcquired = true;
|
|
31
|
+
|
|
32
|
+
console.log('[WakeLock] ✅ Wake lock acquired (or skipped on non-Termux)');
|
|
33
|
+
|
|
34
|
+
res.json({
|
|
35
|
+
status: 'ok',
|
|
36
|
+
message: 'Wake lock acquired',
|
|
37
|
+
acquired: wakeLockAcquired,
|
|
38
|
+
platform: isTermux ? 'termux' : 'linux'
|
|
39
|
+
});
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error('[WakeLock] ❌ Unexpected error:', err.message);
|
|
42
|
+
|
|
43
|
+
// Still return success to avoid breaking the app
|
|
44
|
+
res.status(200).json({
|
|
45
|
+
status: 'ok',
|
|
46
|
+
message: 'Wake lock handler executed',
|
|
47
|
+
acquired: true,
|
|
48
|
+
error: err.message // Log the error but don't fail the request
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* DELETE /api/v1/wake-lock
|
|
55
|
+
* Release wake lock
|
|
56
|
+
*/
|
|
57
|
+
router.delete('/wake-lock', (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
console.log('[WakeLock] Releasing wake lock...');
|
|
60
|
+
|
|
61
|
+
// Execute termux-wake-unlock
|
|
62
|
+
execSync('termux-wake-unlock', { stdio: 'ignore' });
|
|
63
|
+
|
|
64
|
+
wakeLockAcquired = false;
|
|
65
|
+
|
|
66
|
+
console.log('[WakeLock] ✅ Wake lock released');
|
|
67
|
+
|
|
68
|
+
res.json({
|
|
69
|
+
status: 'ok',
|
|
70
|
+
message: 'Wake lock released',
|
|
71
|
+
acquired: wakeLockAcquired
|
|
72
|
+
});
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error('[WakeLock] ❌ Failed to release wake lock:', err.message);
|
|
75
|
+
|
|
76
|
+
res.status(500).json({
|
|
77
|
+
status: 'error',
|
|
78
|
+
message: 'Failed to release wake lock',
|
|
79
|
+
error: err.message
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* GET /api/v1/wake-lock
|
|
86
|
+
* Get wake lock status
|
|
87
|
+
*/
|
|
88
|
+
router.get('/wake-lock', (req, res) => {
|
|
89
|
+
res.json({
|
|
90
|
+
status: 'ok',
|
|
91
|
+
acquired: wakeLockAcquired
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
module.exports = router;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Routes - Express router for workspace management
|
|
3
|
+
*
|
|
4
|
+
* Endpoints:
|
|
5
|
+
* - GET /api/v1/workspaces - List discovered workspaces
|
|
6
|
+
* - POST /api/v1/workspaces/mount - Validate and set active workspace
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const express = require('express');
|
|
10
|
+
const router = express.Router();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* GET /api/v1/workspaces
|
|
14
|
+
* List all discovered workspaces from conversations table
|
|
15
|
+
*/
|
|
16
|
+
router.get('/', (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const WorkspaceManager = require('../services/workspace-manager');
|
|
19
|
+
const wm = new WorkspaceManager();
|
|
20
|
+
const workspaces = wm.discoverWorkspaces();
|
|
21
|
+
|
|
22
|
+
res.json({
|
|
23
|
+
workspaces,
|
|
24
|
+
count: workspaces.length
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('[Workspaces] Error listing workspaces:', error);
|
|
28
|
+
res.status(500).json({ error: 'Failed to list workspaces' });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* POST /api/v1/workspaces/mount
|
|
34
|
+
* Validate workspace path and optionally return workspace details
|
|
35
|
+
* Body: { path: string }
|
|
36
|
+
*/
|
|
37
|
+
router.post('/mount', (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const { path: wsPath } = req.body;
|
|
40
|
+
|
|
41
|
+
if (!wsPath) {
|
|
42
|
+
return res.status(400).json({ error: 'Workspace path is required' });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const WorkspaceManager = require('../services/workspace-manager');
|
|
46
|
+
const wm = new WorkspaceManager();
|
|
47
|
+
|
|
48
|
+
// Validate workspace exists
|
|
49
|
+
const valid = wm.validateWorkspace(wsPath);
|
|
50
|
+
|
|
51
|
+
if (!valid) {
|
|
52
|
+
return res.status(400).json({ error: 'Invalid workspace path' });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Get workspace details if it exists in conversations
|
|
56
|
+
const stats = wm.getWorkspaceStats(wsPath);
|
|
57
|
+
const conversations = wm.getWorkspaceConversations(wsPath);
|
|
58
|
+
|
|
59
|
+
res.json({
|
|
60
|
+
workspace: wsPath,
|
|
61
|
+
mounted: true,
|
|
62
|
+
stats,
|
|
63
|
+
conversations: conversations.slice(0, 10) // Return first 10
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('[Workspaces] Error mounting workspace:', error);
|
|
67
|
+
res.status(500).json({ error: 'Failed to mount workspace' });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* GET /api/v1/workspaces/:path/stats
|
|
73
|
+
* Get statistics for a specific workspace
|
|
74
|
+
*/
|
|
75
|
+
router.get('/:path/stats', (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const { path: wsPath } = req.params;
|
|
78
|
+
|
|
79
|
+
const WorkspaceManager = require('../services/workspace-manager');
|
|
80
|
+
const wm = new WorkspaceManager();
|
|
81
|
+
|
|
82
|
+
// Validate workspace exists
|
|
83
|
+
const valid = wm.validateWorkspace(wsPath);
|
|
84
|
+
|
|
85
|
+
if (!valid) {
|
|
86
|
+
return res.status(404).json({ error: 'Workspace not found' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const stats = wm.getWorkspaceStats(wsPath);
|
|
90
|
+
|
|
91
|
+
res.json({
|
|
92
|
+
workspace: wsPath,
|
|
93
|
+
stats
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('[Workspaces] Error getting workspace stats:', error);
|
|
97
|
+
res.status(500).json({ error: 'Failed to get workspace stats' });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
module.exports = router;
|
package/lib/server/server.js
CHANGED
|
@@ -10,18 +10,29 @@ const cors = require('cors');
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const http = require('http');
|
|
13
|
-
const https = require('https');
|
|
14
13
|
|
|
14
|
+
const { initDb, saveDb } = require('./db/adapter');
|
|
15
15
|
const TmuxManager = require('../services/tmux-manager');
|
|
16
16
|
const EngineDiscovery = require('../services/engine-discovery');
|
|
17
17
|
const SessionStore = require('../services/session-store');
|
|
18
18
|
const LogWatcher = require('../services/log-watcher');
|
|
19
19
|
|
|
20
|
+
// Routes
|
|
20
21
|
const sendRouter = require('./routes/send');
|
|
21
22
|
const sessionsRouter = require('./routes/sessions');
|
|
22
23
|
const modelsRouter = require('./routes/models');
|
|
24
|
+
const runtimesRouter = require('./routes/runtimes');
|
|
23
25
|
const statusRouter = require('./routes/status');
|
|
24
26
|
const hostsRouter = require('./routes/hosts');
|
|
27
|
+
const wakeLockRouter = require('./routes/wake-lock');
|
|
28
|
+
const authRouter = require('./routes/auth');
|
|
29
|
+
const speechRouter = require('./routes/speech');
|
|
30
|
+
const keysRouter = require('./routes/keys');
|
|
31
|
+
const uploadRouter = require('./routes/upload');
|
|
32
|
+
const workspacesRouter = require('./routes/workspaces');
|
|
33
|
+
|
|
34
|
+
// Middleware
|
|
35
|
+
const { authMiddleware } = require('./middleware/auth');
|
|
25
36
|
|
|
26
37
|
class NexusCrewServer {
|
|
27
38
|
constructor(config = {}) {
|
|
@@ -49,7 +60,17 @@ class NexusCrewServer {
|
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
async start() {
|
|
52
|
-
// Init
|
|
63
|
+
// 1. Init database
|
|
64
|
+
await initDb();
|
|
65
|
+
|
|
66
|
+
// 2. Seed admin user if no users exist
|
|
67
|
+
const User = require('./models/User');
|
|
68
|
+
if (User.count() === 0) {
|
|
69
|
+
User.create('admin', 'admin', 'admin');
|
|
70
|
+
console.log(' Created default admin user (admin/admin)');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 3. Init services
|
|
53
74
|
const hosts = this._loadHosts();
|
|
54
75
|
const tmuxManager = new TmuxManager({
|
|
55
76
|
tmuxSession: this.config.tmuxSession || 'nexuscrew',
|
|
@@ -58,7 +79,8 @@ class NexusCrewServer {
|
|
|
58
79
|
});
|
|
59
80
|
|
|
60
81
|
const engineDiscovery = new EngineDiscovery(this.config.providersPath);
|
|
61
|
-
const sessionStore = new SessionStore(
|
|
82
|
+
const sessionStore = new SessionStore();
|
|
83
|
+
await sessionStore.init();
|
|
62
84
|
const logWatcher = new LogWatcher();
|
|
63
85
|
|
|
64
86
|
// Ensure tmux master session
|
|
@@ -75,23 +97,23 @@ class NexusCrewServer {
|
|
|
75
97
|
}
|
|
76
98
|
}
|
|
77
99
|
|
|
78
|
-
// Middleware
|
|
100
|
+
// 4. Middleware
|
|
79
101
|
this.app.use(cors());
|
|
80
102
|
this.app.use(express.json({ limit: '10mb' }));
|
|
81
103
|
|
|
82
|
-
// Health check
|
|
104
|
+
// 5. Health check (public)
|
|
83
105
|
this.app.get('/health', (req, res) => {
|
|
84
106
|
res.json({
|
|
85
107
|
status: 'ok',
|
|
86
108
|
service: 'nexuscrew',
|
|
87
|
-
version: '0.
|
|
109
|
+
version: '0.2.0',
|
|
88
110
|
tmux: tmuxManager.hasSession(),
|
|
89
111
|
windows: tmuxManager.listWindows().length,
|
|
90
112
|
uptime: process.uptime()
|
|
91
113
|
});
|
|
92
114
|
});
|
|
93
115
|
|
|
94
|
-
// Inject services into request
|
|
116
|
+
// 6. Inject services into request
|
|
95
117
|
this.app.use((req, res, next) => {
|
|
96
118
|
req.tmux = tmuxManager;
|
|
97
119
|
req.engines = engineDiscovery;
|
|
@@ -101,14 +123,23 @@ class NexusCrewServer {
|
|
|
101
123
|
next();
|
|
102
124
|
});
|
|
103
125
|
|
|
104
|
-
//
|
|
105
|
-
this.app.use('/api/v1/
|
|
106
|
-
this.app.use('/api/v1
|
|
126
|
+
// 7. Public routes
|
|
127
|
+
this.app.use('/api/v1/auth', authRouter);
|
|
128
|
+
this.app.use('/api/v1', wakeLockRouter);
|
|
129
|
+
this.app.use('/api/v1/runtimes', runtimesRouter); // public for login screen
|
|
107
130
|
this.app.use('/api/v1/models', modelsRouter);
|
|
108
|
-
this.app.use('/api/v1/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
this.app.use('/api/v1/keys', keysRouter); // public for STT key check
|
|
132
|
+
|
|
133
|
+
// 8. Protected routes
|
|
134
|
+
this.app.use('/api/v1/send', authMiddleware, sendRouter);
|
|
135
|
+
this.app.use('/api/v1/sessions', authMiddleware, sessionsRouter);
|
|
136
|
+
this.app.use('/api/v1/workspaces', authMiddleware, workspacesRouter);
|
|
137
|
+
this.app.use('/api/v1/status', authMiddleware, statusRouter);
|
|
138
|
+
this.app.use('/api/v1/hosts', authMiddleware, hostsRouter);
|
|
139
|
+
this.app.use('/api/v1/speech', authMiddleware, speechRouter);
|
|
140
|
+
this.app.use('/api/v1/upload', authMiddleware, uploadRouter);
|
|
141
|
+
|
|
142
|
+
// 9. Serve frontend
|
|
112
143
|
const frontendDist = path.join(__dirname, '../../frontend/dist');
|
|
113
144
|
if (fs.existsSync(frontendDist)) {
|
|
114
145
|
this.app.use(express.static(frontendDist));
|
|
@@ -119,13 +150,13 @@ class NexusCrewServer {
|
|
|
119
150
|
});
|
|
120
151
|
}
|
|
121
152
|
|
|
122
|
-
// Error handler
|
|
153
|
+
// 10. Error handler
|
|
123
154
|
this.app.use((err, req, res, next) => {
|
|
124
155
|
console.error('[Error]', err.message);
|
|
125
156
|
res.status(500).json({ error: err.message });
|
|
126
157
|
});
|
|
127
158
|
|
|
128
|
-
// Start HTTP server
|
|
159
|
+
// 11. Start HTTP server
|
|
129
160
|
this.server = http.createServer(this.app);
|
|
130
161
|
|
|
131
162
|
return new Promise((resolve) => {
|
|
@@ -153,6 +184,7 @@ class NexusCrewServer {
|
|
|
153
184
|
|
|
154
185
|
stop() {
|
|
155
186
|
if (this.server) {
|
|
187
|
+
saveDb();
|
|
156
188
|
this.server.close();
|
|
157
189
|
}
|
|
158
190
|
}
|