@mmmbuto/nexuscrew 0.2.5 → 0.4.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 (94) hide show
  1. package/README.md +63 -163
  2. package/bin/nexuscrew.js +1 -296
  3. package/frontend/dist/assets/index-B41xYEDb.js +81 -0
  4. package/frontend/dist/assets/index-Cjqxzf2o.css +32 -0
  5. package/frontend/dist/icon-maskable.svg +4 -0
  6. package/frontend/dist/icon.svg +4 -0
  7. package/frontend/dist/index.html +11 -3
  8. package/frontend/dist/manifest.json +24 -0
  9. package/frontend/dist/sw.js +3 -0
  10. package/frontend/index.html +20 -0
  11. package/lib/auth/token.js +26 -0
  12. package/lib/config.js +25 -0
  13. package/lib/pty/attach.js +30 -0
  14. package/lib/pty/provider.js +33 -0
  15. package/lib/server.js +62 -0
  16. package/lib/tmux/actions.js +27 -0
  17. package/lib/tmux/list.js +33 -0
  18. package/lib/ws/bridge.js +66 -0
  19. package/package.json +20 -24
  20. package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  21. package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  22. package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  23. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  24. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  25. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  26. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  27. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  28. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  29. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  30. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  31. package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  32. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  33. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  34. package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  35. package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  36. package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  37. package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  38. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  39. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  40. package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  41. package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  42. package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  43. package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  44. package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  45. package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  46. package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  47. package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  48. package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  49. package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  50. package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  51. package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  52. package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  53. package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  54. package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  55. package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  56. package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  57. package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  58. package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  59. package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  60. package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  61. package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  62. package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  63. package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  64. package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  65. package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  66. package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  67. package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  68. package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  69. package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  70. package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  71. package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  72. package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  73. package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  74. package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  75. package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  76. package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  77. package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  78. package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  79. package/frontend/dist/assets/index-CSPyKzmq.js +0 -8614
  80. package/frontend/dist/assets/index-Cl7bxc7X.css +0 -1
  81. package/lib/config/paths.js +0 -30
  82. package/lib/server/routes/hosts.js +0 -82
  83. package/lib/server/routes/launchers.js +0 -17
  84. package/lib/server/routes/models.js +0 -26
  85. package/lib/server/routes/runtime.js +0 -14
  86. package/lib/server/routes/send.js +0 -236
  87. package/lib/server/routes/sessions.js +0 -104
  88. package/lib/server/routes/status.js +0 -62
  89. package/lib/server/routes/tmux.js +0 -91
  90. package/lib/server/server.js +0 -189
  91. package/lib/services/engine-discovery.js +0 -317
  92. package/lib/services/log-watcher.js +0 -279
  93. package/lib/services/session-store.js +0 -198
  94. package/lib/services/tmux-manager.js +0 -288
@@ -1,104 +0,0 @@
1
- /**
2
- * GET /api/v1/sessions — List/load sessions
3
- * GET /api/v1/sessions/:id/messages — Get session messages
4
- * GET /api/v1/sessions/:id/summary — Get session summary
5
- * POST /api/v1/sessions/:id/rename — Rename session
6
- * DELETE /api/v1/sessions/:id — Delete session
7
- */
8
-
9
- const { Router } = require('express');
10
- const router = Router();
11
-
12
- // List conversations (optionally grouped by date)
13
- router.get('/', (req, res) => {
14
- const { store } = req;
15
- const { workspace, groupBy } = req.query;
16
-
17
- if (groupBy === 'date') {
18
- const grouped = store.listGroupedByDate({ workspace });
19
- return res.json(grouped);
20
- }
21
-
22
- const convs = store.listConversations({ workspace });
23
- res.json({ conversations: convs });
24
- });
25
-
26
- // Get single conversation
27
- router.get('/:id', (req, res) => {
28
- const { store, tmux } = req;
29
- const conv = store.getConversation(req.params.id);
30
-
31
- if (!conv) {
32
- return res.status(404).json({ error: 'Not found' });
33
- }
34
-
35
- // Check tmux window status
36
- const host = conv.host !== 'local' ? conv.host : null;
37
- const target = conv.tmux_session || conv.tmux_window;
38
- const alive = target ? tmux.hasSession(target, host) : false;
39
-
40
- res.json({
41
- session: {
42
- ...conv,
43
- tmux_alive: alive
44
- }
45
- });
46
- });
47
-
48
- // Get messages for a session
49
- router.get('/:id/messages', (req, res) => {
50
- const { store } = req;
51
- const { limit = 50, before } = req.query;
52
-
53
- const conv = store.getConversation(req.params.id);
54
- if (!conv) {
55
- return res.status(404).json({ error: 'Not found' });
56
- }
57
-
58
- const messages = store.getMessages(req.params.id, { limit: Number(limit), before: before ? Number(before) : undefined });
59
- const total = store.getMessageCount(req.params.id);
60
-
61
- res.json({
62
- session: conv,
63
- messages,
64
- pagination: {
65
- total,
66
- hasMore: messages.length < total,
67
- oldestTimestamp: messages.length > 0 ? messages[0].created_at : null
68
- }
69
- });
70
- });
71
-
72
- // Rename conversation
73
- router.post('/:id/rename', (req, res) => {
74
- const { store } = req;
75
- const { title } = req.body;
76
-
77
- if (!title) return res.status(400).json({ error: 'Title required' });
78
-
79
- store.updateConversation(req.params.id, { title });
80
- res.json({ success: true, title });
81
- });
82
-
83
- // Delete conversation (kills tmux session too)
84
- router.delete('/:id', (req, res) => {
85
- const { store, tmux, logWatcher } = req;
86
- const conv = store.getConversation(req.params.id);
87
-
88
- if (!conv) return res.status(404).json({ error: 'Not found' });
89
-
90
- if (conv.tmux_session) {
91
- const host = conv.host !== 'local' ? conv.host : null;
92
- tmux.killSession(conv.tmux_session, host);
93
- }
94
-
95
- // Stop log watcher
96
- if (conv.log_path) {
97
- logWatcher.unwatch(conv.log_path);
98
- }
99
-
100
- store.deleteConversation(req.params.id);
101
- res.json({ success: true });
102
- });
103
-
104
- module.exports = router;
@@ -1,62 +0,0 @@
1
- /**
2
- * GET /api/v1/status — tmux status
3
- * GET /api/v1/status/tmux — Detailed tmux session info
4
- * GET /api/v1/status/pane/:session — Capture pane content
5
- */
6
-
7
- const { Router } = require('express');
8
- const router = Router();
9
-
10
- // Overall status
11
- router.get('/', (req, res) => {
12
- const { tmux } = req;
13
- const host = req.query.host || 'local';
14
- const sessions = tmux.listSessions(host === 'local' ? null : host);
15
- const hosts = tmux.getAllHosts();
16
-
17
- res.json({
18
- host,
19
- tmux: {
20
- sessionCount: sessions.length,
21
- sessions
22
- },
23
- hosts: hosts.map(h => ({
24
- ...h,
25
- connected: tmux.testHost(h)
26
- }))
27
- });
28
- });
29
-
30
- // Detailed tmux windows
31
- router.get('/tmux', (req, res) => {
32
- const { tmux, store } = req;
33
- const host = req.query.host || 'local';
34
- const sessions = tmux.listSessions(host === 'local' ? null : host);
35
- const conversations = store.listConversations({ host, limit: 500 });
36
-
37
- const enriched = sessions.map((session) => {
38
- const conv = conversations.find((item) => item.tmux_session === session.sessionName);
39
- return {
40
- ...session,
41
- conversationId: conv?.id || null,
42
- engine: conv?.engine || null,
43
- model: conv?.model || null,
44
- host,
45
- title: conv?.title || session.sessionName,
46
- workspace: conv?.workspace || ''
47
- };
48
- });
49
-
50
- res.json({ host, sessions: enriched });
51
- });
52
-
53
- // Capture pane content
54
- router.get('/pane/:session', (req, res) => {
55
- const { tmux } = req;
56
- const { host } = req.query;
57
- const content = tmux.capturePane(req.params.session, host || null);
58
-
59
- res.json({ session: req.params.session, content });
60
- });
61
-
62
- module.exports = router;
@@ -1,91 +0,0 @@
1
- const { Router } = require('express');
2
-
3
- const router = Router();
4
-
5
- router.get('/sessions', (req, res) => {
6
- const { tmux, store } = req;
7
- const host = req.query.host || 'local';
8
- const resolvedHost = host === 'local' ? null : host;
9
- const sessions = tmux.listSessions(resolvedHost).map((session) => {
10
- const conversation = store.listConversations({ host, limit: 500 })
11
- .find((item) => item.tmux_session === session.sessionName);
12
- return {
13
- ...session,
14
- host,
15
- workspace: conversation?.workspace || '',
16
- conversationId: conversation?.id || null,
17
- launcherId: conversation?.launcher_id || null,
18
- engine: conversation?.engine || null,
19
- status: conversation?.status || 'external'
20
- };
21
- });
22
-
23
- res.json({ sessions });
24
- });
25
-
26
- router.post('/sessions', (req, res) => {
27
- const { tmux, store, engines } = req;
28
- const {
29
- sessionName,
30
- launcherId,
31
- workspace,
32
- host
33
- } = req.body;
34
-
35
- if (!sessionName || !sessionName.trim()) {
36
- return res.status(400).json({ error: 'sessionName is required' });
37
- }
38
- if (!launcherId) {
39
- return res.status(400).json({ error: 'launcherId is required' });
40
- }
41
-
42
- const launcher = engines.getLauncher(launcherId, host || 'local');
43
- if (!launcher) {
44
- return res.status(404).json({ error: `Launcher "${launcherId}" not found` });
45
- }
46
- if (!launcher.runnable || launcher.bucket !== 'runnable') {
47
- return res.status(409).json({
48
- error: `Launcher "${launcher.label}" was detected from config but is not verified runnable in the current shell`
49
- });
50
- }
51
-
52
- const targetHost = host || 'local';
53
- if (tmux.hasSession(sessionName.trim(), targetHost)) {
54
- return res.status(409).json({ error: `tmux session "${sessionName.trim()}" already exists` });
55
- }
56
-
57
- const runtime = engines.getRuntimeInfo();
58
- const createResult = tmux.createSession({
59
- sessionName: sessionName.trim(),
60
- cwd: (workspace || runtime.homeDir || '').trim() || runtime.homeDir,
61
- host: targetHost,
62
- shell: runtime.shell,
63
- launcherCommand: launcher.command
64
- });
65
-
66
- const conversation = store.createConversation({
67
- title: sessionName.trim(),
68
- engine: launcher.family,
69
- workspace: (workspace || runtime.homeDir || '').trim() || runtime.homeDir,
70
- host: targetHost,
71
- tmuxSession: createResult.sessionName,
72
- logPath: createResult.logPath,
73
- launcherId: launcher.id
74
- });
75
- store.updateConversation(conversation.id, { title: sessionName.trim() });
76
-
77
- res.status(201).json({
78
- session: {
79
- sessionName: createResult.sessionName,
80
- logPath: createResult.logPath,
81
- host: targetHost,
82
- workspace: conversation.workspace,
83
- conversationId: conversation.id,
84
- launcherId: launcher.id,
85
- engine: launcher.family,
86
- runnable: true
87
- }
88
- });
89
- });
90
-
91
- module.exports = router;
@@ -1,189 +0,0 @@
1
- /**
2
- * NexusCrew Server — tmux-based AI cockpit
3
- *
4
- * Unified endpoint: POST /api/v1/send
5
- * All engines routed through tmux windows.
6
- */
7
-
8
- const express = require('express');
9
- const cors = require('cors');
10
- const path = require('path');
11
- const fs = require('fs');
12
- const http = require('http');
13
- const os = require('os');
14
- const packageJson = require('../../package.json');
15
- const { getConfigDir, getDefaultLogDir } = require('../config/paths');
16
-
17
- const TmuxManager = require('../services/tmux-manager');
18
- const EngineDiscovery = require('../services/engine-discovery');
19
- const SessionStore = require('../services/session-store');
20
- const LogWatcher = require('../services/log-watcher');
21
-
22
- const sendRouter = require('./routes/send');
23
- const sessionsRouter = require('./routes/sessions');
24
- const modelsRouter = require('./routes/models');
25
- const statusRouter = require('./routes/status');
26
- const hostsRouter = require('./routes/hosts');
27
- const tmuxRouter = require('./routes/tmux');
28
- const launchersRouter = require('./routes/launchers');
29
- const runtimeRouter = require('./routes/runtime');
30
-
31
- class NexusCrewServer {
32
- constructor(config = {}) {
33
- this.port = config.port || process.env.PORT || 41820;
34
- this.configDir = config.configDir || getConfigDir();
35
- this.config = this._loadConfig();
36
- this.app = express();
37
- this.server = null;
38
- }
39
-
40
- _loadConfig() {
41
- const configPath = path.join(this.configDir, 'config.json');
42
- if (fs.existsSync(configPath)) {
43
- const loaded = JSON.parse(fs.readFileSync(configPath, 'utf8'));
44
- if (loaded.providersPath && !loaded.extraShellSources) {
45
- loaded.extraShellSources = [loaded.providersPath].filter(Boolean);
46
- }
47
- return loaded;
48
- }
49
- return {
50
- port: this.port,
51
- autoDiscovery: true,
52
- preferredShell: '',
53
- extraShellSources: []
54
- };
55
- }
56
-
57
- _loadHosts() {
58
- const hostsPath = path.join(this.configDir, 'hosts.json');
59
- if (fs.existsSync(hostsPath)) {
60
- return JSON.parse(fs.readFileSync(hostsPath, 'utf8'));
61
- }
62
- return [{ name: 'local', type: 'local', default: true }];
63
- }
64
-
65
- async start() {
66
- const hosts = this._loadHosts();
67
- const tmuxManager = new TmuxManager({
68
- logDir: this.config.logDir || getDefaultLogDir(),
69
- configDir: this.configDir,
70
- preferredShell: this.config.preferredShell || process.env.SHELL || '/bin/sh',
71
- hosts
72
- });
73
-
74
- const engineDiscovery = new EngineDiscovery({
75
- tmux: tmuxManager,
76
- preferredShell: this.config.preferredShell,
77
- extraShellSources: this.config.extraShellSources || []
78
- });
79
- const sessionStore = new SessionStore(path.join(this.configDir, 'nexuscrew.db'));
80
- const logWatcher = new LogWatcher();
81
-
82
- sessionStore.syncWithTmux(tmuxManager);
83
-
84
- const active = sessionStore.listConversations({ status: 'active' });
85
- for (const conv of active) {
86
- if ((conv.host || 'local') === 'local' && conv.log_path && fs.existsSync(conv.log_path)) {
87
- logWatcher.watch(conv.log_path, conv.engine || 'generic', conv.id);
88
- }
89
- }
90
-
91
- // Middleware
92
- this.app.use(cors());
93
- this.app.use(express.json({ limit: '10mb' }));
94
-
95
- // Health check
96
- this.app.get('/health', (req, res) => {
97
- res.json({
98
- status: 'ok',
99
- service: 'nexuscrew',
100
- version: packageJson.version,
101
- tmuxSessions: tmuxManager.listSessions().length,
102
- uptime: process.uptime()
103
- });
104
- });
105
-
106
- // Inject services into request
107
- this.app.use((req, res, next) => {
108
- req.tmux = tmuxManager;
109
- req.engines = engineDiscovery;
110
- req.store = sessionStore;
111
- req.logWatcher = logWatcher;
112
- req.config = this.config;
113
- req.configDir = this.configDir;
114
- next();
115
- });
116
-
117
- // API routes
118
- this.app.use('/api/v1/send', sendRouter);
119
- this.app.use('/api/v1/sessions', sessionsRouter);
120
- this.app.use('/api/v1/models', modelsRouter);
121
- this.app.use('/api/v1/status', statusRouter);
122
- this.app.use('/api/v1/hosts', hostsRouter);
123
- this.app.use('/api/v1/tmux', tmuxRouter);
124
- this.app.use('/api/v1/launchers', launchersRouter);
125
- this.app.use('/api/v1/runtime', runtimeRouter);
126
-
127
- // Serve frontend
128
- const frontendDist = path.join(__dirname, '../../frontend/dist');
129
- if (fs.existsSync(frontendDist)) {
130
- this.app.use(express.static(frontendDist));
131
- this.app.get('*', (req, res) => {
132
- if (!req.path.startsWith('/api/')) {
133
- res.sendFile(path.join(frontendDist, 'index.html'));
134
- }
135
- });
136
- }
137
-
138
- // Error handler
139
- this.app.use((err, req, res, next) => {
140
- console.error('[Error]', err.message);
141
- res.status(500).json({ error: err.message });
142
- });
143
-
144
- // Start HTTP server
145
- this.server = http.createServer(this.app);
146
-
147
- return new Promise((resolve) => {
148
- this.server.listen(this.port, () => {
149
- console.log('');
150
- console.log('╔══════════════════════════════════════════════╗');
151
- console.log('║ NexusCrew — tmux AI cockpit ║');
152
- console.log('╚══════════════════════════════════════════════╝');
153
- console.log('');
154
- console.log(` HTTP: http://localhost:${this.port}`);
155
- console.log(` tmux: ${tmuxManager.listSessions().length} active sessions`);
156
- console.log(` Hosts: ${hosts.map(h => h.name).join(', ')}`);
157
- console.log('');
158
-
159
- const discovery = engineDiscovery.discover();
160
- for (const launcher of discovery.launchers) {
161
- console.log(` OK ${launcher.label} [${launcher.kind}]`);
162
- }
163
- console.log('');
164
-
165
- resolve();
166
- });
167
- });
168
- }
169
-
170
- stop() {
171
- if (this.server) {
172
- this.server.close();
173
- }
174
- }
175
- }
176
-
177
- // Run if called directly
178
- if (require.main === module) {
179
- const server = new NexusCrewServer();
180
- server.start().catch(err => {
181
- console.error('Failed to start:', err);
182
- process.exit(1);
183
- });
184
-
185
- process.on('SIGTERM', () => { server.stop(); process.exit(0); });
186
- process.on('SIGINT', () => { console.log('\nShutting down...'); server.stop(); process.exit(0); });
187
- }
188
-
189
- module.exports = NexusCrewServer;