@mmmbuto/nexuscli 0.5.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 (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/bin/nexuscli.js +117 -0
  4. package/frontend/dist/apple-touch-icon.png +0 -0
  5. package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  6. package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  7. package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  8. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  9. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  10. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  11. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  12. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  13. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  14. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  15. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  16. package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  17. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  18. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  19. package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  20. package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  21. package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  22. package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  23. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  24. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  25. package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  26. package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  27. package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  28. package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  29. package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  30. package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  31. package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  32. package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  33. package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  34. package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  35. package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  36. package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  37. package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  38. package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  39. package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  40. package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  41. package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  42. package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  43. package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  44. package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  45. package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  46. package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  47. package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  48. package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  49. package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  50. package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  51. package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  52. package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  53. package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  54. package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  55. package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  56. package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  57. package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  58. package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  59. package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  60. package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  61. package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  62. package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  63. package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  64. package/frontend/dist/assets/index-Bn_l1e6e.css +1 -0
  65. package/frontend/dist/assets/index-CikJbUR5.js +8617 -0
  66. package/frontend/dist/browserconfig.xml +12 -0
  67. package/frontend/dist/favicon-16x16.png +0 -0
  68. package/frontend/dist/favicon-32x32.png +0 -0
  69. package/frontend/dist/favicon-48x48.png +0 -0
  70. package/frontend/dist/favicon.ico +0 -0
  71. package/frontend/dist/icon-192.png +0 -0
  72. package/frontend/dist/icon-512.png +0 -0
  73. package/frontend/dist/icon-maskable-192.png +0 -0
  74. package/frontend/dist/icon-maskable-512.png +0 -0
  75. package/frontend/dist/index.html +79 -0
  76. package/frontend/dist/manifest.json +75 -0
  77. package/frontend/dist/sw.js +122 -0
  78. package/frontend/package.json +28 -0
  79. package/lib/cli/api.js +156 -0
  80. package/lib/cli/boot.js +172 -0
  81. package/lib/cli/config.js +185 -0
  82. package/lib/cli/engines.js +257 -0
  83. package/lib/cli/init.js +660 -0
  84. package/lib/cli/logs.js +72 -0
  85. package/lib/cli/start.js +220 -0
  86. package/lib/cli/status.js +187 -0
  87. package/lib/cli/stop.js +64 -0
  88. package/lib/cli/uninstall.js +194 -0
  89. package/lib/cli/users.js +295 -0
  90. package/lib/cli/workspaces.js +337 -0
  91. package/lib/config/manager.js +233 -0
  92. package/lib/server/.env.example +20 -0
  93. package/lib/server/db/adapter.js +314 -0
  94. package/lib/server/db/drivers/better-sqlite3.js +38 -0
  95. package/lib/server/db/drivers/sql-js.js +75 -0
  96. package/lib/server/db/migrate.js +174 -0
  97. package/lib/server/db/migrations/001_ultra_light_schema.sql +96 -0
  98. package/lib/server/db/migrations/002_session_conversation_mapping.sql +19 -0
  99. package/lib/server/db/migrations/003_message_engine_tracking.sql +18 -0
  100. package/lib/server/db/migrations/004_performance_indexes.sql +16 -0
  101. package/lib/server/db.js +2 -0
  102. package/lib/server/lib/cli-wrapper.js +164 -0
  103. package/lib/server/lib/output-parser.js +132 -0
  104. package/lib/server/lib/pty-adapter.js +57 -0
  105. package/lib/server/middleware/auth.js +103 -0
  106. package/lib/server/models/Conversation.js +259 -0
  107. package/lib/server/models/Message.js +228 -0
  108. package/lib/server/models/User.js +115 -0
  109. package/lib/server/package-lock.json +5895 -0
  110. package/lib/server/routes/auth.js +168 -0
  111. package/lib/server/routes/chat.js +206 -0
  112. package/lib/server/routes/codex.js +205 -0
  113. package/lib/server/routes/conversations.js +224 -0
  114. package/lib/server/routes/gemini.js +228 -0
  115. package/lib/server/routes/jobs.js +317 -0
  116. package/lib/server/routes/messages.js +60 -0
  117. package/lib/server/routes/models.js +198 -0
  118. package/lib/server/routes/sessions.js +285 -0
  119. package/lib/server/routes/upload.js +134 -0
  120. package/lib/server/routes/wake-lock.js +95 -0
  121. package/lib/server/routes/workspace.js +80 -0
  122. package/lib/server/routes/workspaces.js +142 -0
  123. package/lib/server/scripts/cleanup-ghost-sessions.js +71 -0
  124. package/lib/server/scripts/seed-users.js +37 -0
  125. package/lib/server/scripts/test-history-access.js +50 -0
  126. package/lib/server/server.js +227 -0
  127. package/lib/server/services/cache.js +85 -0
  128. package/lib/server/services/claude-wrapper.js +312 -0
  129. package/lib/server/services/cli-loader.js +384 -0
  130. package/lib/server/services/codex-output-parser.js +277 -0
  131. package/lib/server/services/codex-wrapper.js +224 -0
  132. package/lib/server/services/context-bridge.js +289 -0
  133. package/lib/server/services/gemini-output-parser.js +398 -0
  134. package/lib/server/services/gemini-wrapper.js +249 -0
  135. package/lib/server/services/history-sync.js +407 -0
  136. package/lib/server/services/output-parser.js +415 -0
  137. package/lib/server/services/session-manager.js +465 -0
  138. package/lib/server/services/summary-generator.js +259 -0
  139. package/lib/server/services/workspace-manager.js +516 -0
  140. package/lib/server/tests/history-sync.test.js +90 -0
  141. package/lib/server/tests/integration-session-sync.test.js +151 -0
  142. package/lib/server/tests/integration.test.js +76 -0
  143. package/lib/server/tests/performance.test.js +118 -0
  144. package/lib/server/tests/services.test.js +160 -0
  145. package/lib/setup/postinstall.js +216 -0
  146. package/lib/utils/paths.js +107 -0
  147. package/lib/utils/termux.js +145 -0
  148. package/package.json +82 -0
@@ -0,0 +1,317 @@
1
+ const express = require('express');
2
+ const { v4: uuidv4 } = require('uuid');
3
+ const CliWrapper = require('../lib/cli-wrapper');
4
+ const db = require('../db');
5
+
6
+ const router = express.Router();
7
+
8
+ // CLI wrapper instance
9
+ const cliWrapper = new CliWrapper({
10
+ workspaceDir: process.cwd()
11
+ });
12
+
13
+ /**
14
+ * POST /api/v1/jobs
15
+ * Create and execute job
16
+ */
17
+ router.post('/', async (req, res) => {
18
+ try {
19
+ const {
20
+ conversationId,
21
+ messageId,
22
+ nodeId = 'localhost',
23
+ tool = 'bash',
24
+ command,
25
+ workingDir,
26
+ timeout = 30000
27
+ } = req.body;
28
+
29
+ // Validate input
30
+ if (!command) {
31
+ return res.status(400).json({ error: 'Command is required' });
32
+ }
33
+
34
+ // Create job ID
35
+ const jobId = uuidv4();
36
+ const now = Date.now();
37
+
38
+ // Insert job into database
39
+ const stmt = db.prepare(`
40
+ INSERT INTO jobs (
41
+ id, conversation_id, message_id, node_id, tool, command,
42
+ status, created_at
43
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
44
+ `);
45
+
46
+ stmt.run(
47
+ jobId,
48
+ conversationId || null,
49
+ messageId || null,
50
+ nodeId,
51
+ tool,
52
+ command,
53
+ 'queued',
54
+ now
55
+ );
56
+
57
+ console.log(`[Jobs] Created job ${jobId}: ${tool} - ${command.substring(0, 50)}`);
58
+
59
+ // Return job info with stream endpoint
60
+ res.status(201).json({
61
+ jobId,
62
+ nodeId,
63
+ tool,
64
+ command,
65
+ status: 'queued',
66
+ createdAt: now,
67
+ streamEndpoint: `/api/v1/jobs/${jobId}/stream`
68
+ });
69
+
70
+ // Execute job asynchronously (don't block response)
71
+ setImmediate(() => {
72
+ executeJob(jobId, { tool, command, workingDir, timeout });
73
+ });
74
+
75
+ } catch (error) {
76
+ console.error('[Jobs] Create error:', error);
77
+ res.status(500).json({ error: 'Failed to create job' });
78
+ }
79
+ });
80
+
81
+ /**
82
+ * GET /api/v1/jobs/:jobId/stream
83
+ * SSE stream for job execution
84
+ */
85
+ router.get('/:jobId/stream', (req, res) => {
86
+ const { jobId } = req.params;
87
+
88
+ console.log(`[Jobs] SSE stream opened for job ${jobId}`);
89
+
90
+ // Set SSE headers (LibreChat/NexusChat pattern)
91
+ res.writeHead(200, {
92
+ 'Content-Type': 'text/event-stream',
93
+ 'Cache-Control': 'no-cache',
94
+ 'Connection': 'keep-alive',
95
+ 'X-Accel-Buffering': 'no', // Disable nginx buffering
96
+ });
97
+
98
+ // Get job from database
99
+ const stmt = db.prepare('SELECT * FROM jobs WHERE id = ?');
100
+ const job = stmt.get(jobId);
101
+
102
+ if (!job) {
103
+ res.write(`data: ${JSON.stringify({
104
+ type: 'error',
105
+ error: 'Job not found'
106
+ })}\n\n`);
107
+ res.end();
108
+ return;
109
+ }
110
+
111
+ // If job already completed, send final result
112
+ if (job.status === 'completed' || job.status === 'failed') {
113
+ res.write(`data: ${JSON.stringify({
114
+ type: 'response_done',
115
+ exitCode: job.exit_code,
116
+ duration: job.duration
117
+ })}\n\n`);
118
+
119
+ res.write(`data: ${JSON.stringify({
120
+ type: 'done'
121
+ })}\n\n`);
122
+
123
+ res.end();
124
+ return;
125
+ }
126
+
127
+ // Store SSE connection in a Map (keyed by jobId)
128
+ if (!global.sseConnections) {
129
+ global.sseConnections = new Map();
130
+ }
131
+
132
+ global.sseConnections.set(jobId, res);
133
+
134
+ // Emit initial queued status
135
+ res.write(`data: ${JSON.stringify({
136
+ type: 'status',
137
+ category: 'queued',
138
+ nodeId: job.node_id,
139
+ message: `Queued on ${job.node_id}...`,
140
+ icon: '⏱️',
141
+ timestamp: new Date().toISOString()
142
+ })}\n\n`);
143
+
144
+ // Cleanup on client disconnect
145
+ req.on('close', () => {
146
+ console.log(`[Jobs] SSE stream closed for job ${jobId}`);
147
+ global.sseConnections.delete(jobId);
148
+ });
149
+ });
150
+
151
+ /**
152
+ * GET /api/v1/jobs/:jobId
153
+ * Get job result
154
+ */
155
+ router.get('/:jobId', (req, res) => {
156
+ try {
157
+ const stmt = db.prepare('SELECT * FROM jobs WHERE id = ?');
158
+ const job = stmt.get(req.params.jobId);
159
+
160
+ if (!job) {
161
+ return res.status(404).json({ error: 'Job not found' });
162
+ }
163
+
164
+ res.json(job);
165
+ } catch (error) {
166
+ console.error('[Jobs] Get error:', error);
167
+ res.status(500).json({ error: 'Failed to get job' });
168
+ }
169
+ });
170
+
171
+ /**
172
+ * DELETE /api/v1/jobs/:jobId
173
+ * Cancel running job
174
+ */
175
+ router.delete('/:jobId', (req, res) => {
176
+ try {
177
+ const { jobId } = req.params;
178
+
179
+ // Try to kill running process
180
+ const killed = cliWrapper.kill(jobId);
181
+
182
+ // Update job status
183
+ const stmt = db.prepare(`
184
+ UPDATE jobs
185
+ SET status = ?, completed_at = ?
186
+ WHERE id = ?
187
+ `);
188
+
189
+ stmt.run('cancelled', Date.now(), jobId);
190
+
191
+ // Emit cancelled event to SSE stream
192
+ emitSSE(jobId, {
193
+ type: 'status',
194
+ category: 'cancelled',
195
+ message: 'Job cancelled',
196
+ icon: '⛔'
197
+ });
198
+
199
+ emitSSE(jobId, { type: 'done' });
200
+
201
+ res.json({ success: true, killed });
202
+ } catch (error) {
203
+ console.error('[Jobs] Cancel error:', error);
204
+ res.status(500).json({ error: 'Failed to cancel job' });
205
+ }
206
+ });
207
+
208
+ /**
209
+ * Execute job asynchronously
210
+ */
211
+ async function executeJob(jobId, { tool, command, workingDir, timeout }) {
212
+ console.log(`[Jobs] Executing job ${jobId}`);
213
+
214
+ // Update status to executing
215
+ let stmt = db.prepare(`
216
+ UPDATE jobs
217
+ SET status = ?, started_at = ?
218
+ WHERE id = ?
219
+ `);
220
+ stmt.run('executing', Date.now(), jobId);
221
+
222
+ // Emit executing status
223
+ emitSSE(jobId, {
224
+ type: 'status',
225
+ category: 'executing',
226
+ message: `Executing on localhost...`,
227
+ icon: '▶',
228
+ timestamp: new Date().toISOString()
229
+ });
230
+
231
+ try {
232
+ // Execute command via CLI wrapper
233
+ const result = await cliWrapper.execute({
234
+ jobId,
235
+ tool,
236
+ command,
237
+ workingDir,
238
+ timeout,
239
+ onStatus: (event) => {
240
+ // Forward events to SSE stream
241
+ emitSSE(jobId, event);
242
+ }
243
+ });
244
+
245
+ // Update job with result
246
+ stmt = db.prepare(`
247
+ UPDATE jobs
248
+ SET status = ?, exit_code = ?, stdout = ?, stderr = ?,
249
+ duration = ?, completed_at = ?
250
+ WHERE id = ?
251
+ `);
252
+
253
+ const status = result.exitCode === 0 ? 'completed' : 'failed';
254
+
255
+ stmt.run(
256
+ status,
257
+ result.exitCode,
258
+ result.stdout,
259
+ result.stderr,
260
+ result.duration,
261
+ Date.now(),
262
+ jobId
263
+ );
264
+
265
+ console.log(`[Jobs] Job ${jobId} ${status} (exit: ${result.exitCode}, ${result.duration}ms)`);
266
+
267
+ // Emit done events
268
+ emitSSE(jobId, {
269
+ type: 'response_done',
270
+ exitCode: result.exitCode,
271
+ duration: result.duration
272
+ });
273
+
274
+ emitSSE(jobId, {
275
+ type: 'done'
276
+ });
277
+
278
+ } catch (error) {
279
+ console.error(`[Jobs] Job ${jobId} error:`, error);
280
+
281
+ // Update job as failed
282
+ stmt = db.prepare(`
283
+ UPDATE jobs
284
+ SET status = ?, stderr = ?, completed_at = ?
285
+ WHERE id = ?
286
+ `);
287
+
288
+ stmt.run('failed', error.message, Date.now(), jobId);
289
+
290
+ // Emit error event
291
+ emitSSE(jobId, {
292
+ type: 'error',
293
+ error: error.message
294
+ });
295
+
296
+ emitSSE(jobId, { type: 'done' });
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Emit SSE event to connected clients
302
+ */
303
+ function emitSSE(jobId, event) {
304
+ if (!global.sseConnections) return;
305
+
306
+ const res = global.sseConnections.get(jobId);
307
+ if (res) {
308
+ try {
309
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
310
+ } catch (error) {
311
+ console.error(`[Jobs] SSE write error for job ${jobId}:`, error);
312
+ global.sseConnections.delete(jobId);
313
+ }
314
+ }
315
+ }
316
+
317
+ module.exports = router;
@@ -0,0 +1,60 @@
1
+ const express = require('express');
2
+ const Message = require('../models/Message');
3
+ const Conversation = require('../models/Conversation');
4
+
5
+ const router = express.Router();
6
+
7
+ /**
8
+ * POST /api/v1/conversations/:conversationId/messages
9
+ * Add message to conversation
10
+ */
11
+ router.post('/:conversationId/messages', (req, res) => {
12
+ try {
13
+ const { conversationId } = req.params;
14
+ const { role, content, metadata } = req.body;
15
+
16
+ // Validate conversation exists
17
+ const conversation = Conversation.getById(conversationId);
18
+ if (!conversation) {
19
+ return res.status(404).json({ error: 'Conversation not found' });
20
+ }
21
+
22
+ // Validate input
23
+ if (!role || !content) {
24
+ return res.status(400).json({ error: 'Role and content are required' });
25
+ }
26
+
27
+ if (!['user', 'assistant', 'system'].includes(role)) {
28
+ return res.status(400).json({ error: 'Invalid role' });
29
+ }
30
+
31
+ // Create message
32
+ const message = Message.create(conversationId, role, content, metadata);
33
+
34
+ res.status(201).json(message);
35
+ } catch (error) {
36
+ console.error('[Messages] Create error:', error);
37
+ res.status(500).json({ error: 'Failed to create message' });
38
+ }
39
+ });
40
+
41
+ /**
42
+ * GET /api/v1/conversations/:conversationId/messages
43
+ * Get messages for conversation
44
+ */
45
+ router.get('/:conversationId/messages', (req, res) => {
46
+ try {
47
+ const { conversationId } = req.params;
48
+ const limit = parseInt(req.query.limit) || 100;
49
+ const offset = parseInt(req.query.offset) || 0;
50
+
51
+ const messages = Message.getByConversation(conversationId, limit, offset);
52
+
53
+ res.json({ messages });
54
+ } catch (error) {
55
+ console.error('[Messages] List error:', error);
56
+ res.status(500).json({ error: 'Failed to list messages' });
57
+ }
58
+ });
59
+
60
+ module.exports = router;
@@ -0,0 +1,198 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ /**
5
+ * GET /api/v1/models
6
+ * Returns list of available CLI tools and their models
7
+ *
8
+ * TRI CLI v0.4.0:
9
+ * - Claude: Opus 4.5, Sonnet 4.5, Haiku 4.5
10
+ * - Codex: GPT-5.1 variants
11
+ * - Gemini: Gemini 3 Pro Preview
12
+ */
13
+ router.get('/', (req, res) => {
14
+ try {
15
+ const cliTools = {
16
+ // ============================================================
17
+ // CLAUDE - Anthropic Claude Code CLI
18
+ // ============================================================
19
+ 'claude': {
20
+ name: 'Claude Code',
21
+ icon: 'Terminal',
22
+ enabled: true,
23
+ endpoint: '/api/v1/chat',
24
+ thinkModes: ['think', 'no-think'],
25
+ defaultThinkMode: 'think',
26
+ models: [
27
+ // === Claude Opus 4.5 (Most Intelligent) ===
28
+ {
29
+ id: 'claude-opus-4-5-20251101',
30
+ name: 'claude-opus-4-5-20251101',
31
+ label: 'Opus 4.5',
32
+ description: '🧠 Most Intelligent',
33
+ category: 'claude'
34
+ },
35
+ // === Claude Sonnet 4.5 (Best Balance) ===
36
+ {
37
+ id: 'claude-sonnet-4-5-20250929',
38
+ name: 'claude-sonnet-4-5-20250929',
39
+ label: 'Sonnet 4.5',
40
+ description: '🧠 Extended Thinking (default)',
41
+ category: 'claude',
42
+ default: true
43
+ },
44
+ // === Claude Haiku 4.5 (Fastest) ===
45
+ {
46
+ id: 'claude-haiku-4-5-20251001',
47
+ name: 'claude-haiku-4-5-20251001',
48
+ label: 'Haiku 4.5',
49
+ description: '⚡ Fast & Efficient',
50
+ category: 'claude'
51
+ },
52
+ // === DeepSeek (Alternative Models) ===
53
+ {
54
+ id: 'deepseek-reasoner',
55
+ name: 'deepseek-reasoner',
56
+ label: 'DeepSeek Reasoner',
57
+ description: '🧠 Deep Reasoning',
58
+ category: 'claude'
59
+ },
60
+ {
61
+ id: 'deepseek-chat',
62
+ name: 'deepseek-chat',
63
+ label: 'DeepSeek Chat',
64
+ description: '💬 Fast Chat',
65
+ category: 'claude'
66
+ }
67
+ ]
68
+ },
69
+
70
+ // ============================================================
71
+ // CODEX - OpenAI Codex CLI
72
+ // ============================================================
73
+ 'codex': {
74
+ name: 'Codex',
75
+ icon: 'Code2',
76
+ enabled: true,
77
+ endpoint: '/api/v1/codex',
78
+ models: [
79
+ {
80
+ id: 'gpt-5.1-codex-max',
81
+ name: 'gpt-5.1-codex-max',
82
+ label: 'GPT-5.1 Codex Max',
83
+ description: '💎 Extra High reasoning (best)',
84
+ category: 'codex',
85
+ reasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
86
+ defaultReasoning: 'xhigh',
87
+ default: true
88
+ },
89
+ {
90
+ id: 'gpt-5.1-codex',
91
+ name: 'gpt-5.1-codex',
92
+ label: 'GPT-5.1 Codex',
93
+ description: '🧠 High reasoning',
94
+ category: 'codex',
95
+ reasoningEfforts: ['low', 'medium', 'high'],
96
+ defaultReasoning: 'high'
97
+ },
98
+ {
99
+ id: 'gpt-5.1-codex-mini',
100
+ name: 'gpt-5.1-codex-mini',
101
+ label: 'GPT-5.1 Codex Mini',
102
+ description: '⚡ Compact & Fast',
103
+ category: 'codex',
104
+ reasoningEfforts: ['medium', 'high'],
105
+ defaultReasoning: 'high'
106
+ },
107
+ {
108
+ id: 'gpt-5.1',
109
+ name: 'gpt-5.1',
110
+ label: 'GPT-5.1',
111
+ description: '🧠 General Purpose',
112
+ category: 'codex',
113
+ reasoningEfforts: ['low', 'medium', 'high'],
114
+ defaultReasoning: 'high'
115
+ }
116
+ ]
117
+ },
118
+
119
+ // ============================================================
120
+ // GEMINI - Google Gemini CLI
121
+ // ============================================================
122
+ 'gemini': {
123
+ name: 'Gemini',
124
+ icon: 'Sparkles',
125
+ enabled: true,
126
+ endpoint: '/api/v1/gemini',
127
+ models: [
128
+ {
129
+ id: 'gemini-3-pro-preview',
130
+ name: 'gemini-3-pro-preview',
131
+ label: 'Gemini 3 Pro',
132
+ description: '🚀 Latest Preview',
133
+ category: 'gemini',
134
+ default: true
135
+ }
136
+ ]
137
+ }
138
+ };
139
+
140
+ res.json(cliTools);
141
+ } catch (error) {
142
+ console.error('[Models] Error fetching models:', error);
143
+ res.status(500).json({ error: 'Failed to fetch models' });
144
+ }
145
+ });
146
+
147
+ /**
148
+ * GET /api/v1/models/:engine
149
+ * Returns models for a specific engine
150
+ */
151
+ router.get('/:engine', (req, res) => {
152
+ try {
153
+ const { engine } = req.params;
154
+
155
+ // Normalize engine name
156
+ let normalizedEngine = engine.toLowerCase();
157
+ if (normalizedEngine.includes('claude')) normalizedEngine = 'claude';
158
+ if (normalizedEngine.includes('codex') || normalizedEngine.includes('openai')) normalizedEngine = 'codex';
159
+ if (normalizedEngine.includes('gemini') || normalizedEngine.includes('google')) normalizedEngine = 'gemini';
160
+
161
+ const cliTools = {
162
+ 'claude': {
163
+ name: 'Claude Code',
164
+ models: [
165
+ { id: 'claude-opus-4-5-20251101', label: 'Opus 4.5', description: '🧠 Most Intelligent' },
166
+ { id: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5', description: '🧠 Extended Thinking', default: true },
167
+ { id: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5', description: '⚡ Fast & Efficient' },
168
+ ]
169
+ },
170
+ 'codex': {
171
+ name: 'Codex',
172
+ models: [
173
+ { id: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max', description: '💎 Best Quality', default: true },
174
+ { id: 'gpt-5.1-codex', label: 'GPT-5.1 Codex', description: '🧠 High reasoning' },
175
+ { id: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini', description: '⚡ Fast' },
176
+ { id: 'gpt-5.1', label: 'GPT-5.1', description: '🧠 General Purpose' },
177
+ ]
178
+ },
179
+ 'gemini': {
180
+ name: 'Gemini',
181
+ models: [
182
+ { id: 'gemini-3-pro-preview', label: 'Gemini 3 Pro', description: '🚀 Latest', default: true },
183
+ ]
184
+ }
185
+ };
186
+
187
+ if (!cliTools[normalizedEngine]) {
188
+ return res.status(404).json({ error: `Engine not found: ${engine}` });
189
+ }
190
+
191
+ res.json(cliTools[normalizedEngine]);
192
+ } catch (error) {
193
+ console.error('[Models] Error fetching engine models:', error);
194
+ res.status(500).json({ error: 'Failed to fetch models' });
195
+ }
196
+ });
197
+
198
+ module.exports = router;