@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.
Files changed (35) hide show
  1. package/README.md +54 -120
  2. package/bin/nexuscrew.js +9 -8
  3. package/frontend/dist/assets/{index-C7bAndew.js → index-8pw4eMB-.js} +1715 -1702
  4. package/frontend/dist/assets/index-BF0tdvNT.css +1 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/lib/config/manager.js +362 -0
  7. package/lib/config/models.js +408 -0
  8. package/lib/server/db/adapter.js +274 -0
  9. package/lib/server/db/drivers/sql-js.js +75 -0
  10. package/lib/server/db/migrate.js +174 -0
  11. package/lib/server/db/migrations/001_base_schema.sql +70 -0
  12. package/lib/server/middleware/auth.js +134 -0
  13. package/lib/server/middleware/rate-limit.js +63 -0
  14. package/lib/server/models/User.js +128 -0
  15. package/lib/server/routes/auth.js +168 -0
  16. package/lib/server/routes/keys.js +15 -0
  17. package/lib/server/routes/models.js +59 -4
  18. package/lib/server/routes/runtimes.js +34 -0
  19. package/lib/server/routes/send.js +28 -4
  20. package/lib/server/routes/sessions.js +34 -2
  21. package/lib/server/routes/speech.js +75 -0
  22. package/lib/server/routes/upload.js +134 -0
  23. package/lib/server/routes/wake-lock.js +95 -0
  24. package/lib/server/routes/workspaces.js +101 -0
  25. package/lib/server/server.js +48 -16
  26. package/lib/server/services/context-bridge.js +425 -0
  27. package/lib/server/services/runtime-manager.js +462 -0
  28. package/lib/server/services/summary-generator.js +309 -0
  29. package/lib/server/services/workspace-manager.js +79 -0
  30. package/lib/services/engine-discovery.js +198 -13
  31. package/lib/services/session-store.js +57 -61
  32. package/lib/utils/paths.js +107 -0
  33. package/lib/utils/termux.js +145 -0
  34. package/package.json +6 -2
  35. 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;
@@ -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 services
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(path.join(this.configDir, 'nexuscrew.db'));
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.1.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
- // API routes
105
- this.app.use('/api/v1/send', sendRouter);
106
- this.app.use('/api/v1/sessions', sessionsRouter);
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/status', statusRouter);
109
- this.app.use('/api/v1/hosts', hostsRouter);
110
-
111
- // Serve frontend
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
  }