@mmmbuto/nexuscli 0.7.0 → 0.7.2

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.
@@ -59,7 +59,7 @@
59
59
 
60
60
  <!-- Prevent Scaling on iOS -->
61
61
  <meta name="format-detection" content="telephone=no" />
62
- <script type="module" crossorigin src="/assets/index-DuqGKSR-.js"></script>
62
+ <script type="module" crossorigin src="/assets/index-DgHku52j.js"></script>
63
63
  <link rel="stylesheet" crossorigin href="/assets/index-Bn_l1e6e.css">
64
64
  </head>
65
65
  <body>
package/lib/cli/model.js CHANGED
@@ -4,9 +4,38 @@
4
4
  */
5
5
 
6
6
  const chalk = require('chalk');
7
- const { getConfig, setConfigValue } = require('../config/manager');
7
+ const {
8
+ getConfig,
9
+ setConfigValue,
10
+ isInitialized
11
+ } = require('../config/manager');
12
+ const {
13
+ getCliTools,
14
+ isValidModelId,
15
+ getAllModels
16
+ } = require('../config/models');
8
17
 
9
18
  async function modelCommand(modelId) {
19
+ if (!isInitialized()) {
20
+ console.log(chalk.red('NexusCLI non è inizializzato.'));
21
+ console.log(chalk.yellow('Esegui prima: nexuscli init'));
22
+ return;
23
+ }
24
+
25
+ const cliTools = getCliTools();
26
+ const allModels = getAllModels();
27
+
28
+ const printAvailableModels = () => {
29
+ console.log(chalk.bold('\nModelli disponibili:'));
30
+ for (const [key, cli] of Object.entries(cliTools)) {
31
+ console.log(chalk.dim(`${cli.name}:`));
32
+ for (const model of cli.models || []) {
33
+ const label = model.label || model.name;
34
+ const defaultTag = model.default ? chalk.green(' (default)') : '';
35
+ console.log(` ${model.id} ${chalk.gray(`- ${label}`)}${defaultTag}`);
36
+ }
37
+ }
38
+ };
10
39
 
11
40
  // If no model specified, show current default
12
41
  if (!modelId) {
@@ -21,23 +50,15 @@ async function modelCommand(modelId) {
21
50
  console.log(chalk.dim('Usage: nexuscli model <model-id>'));
22
51
  }
23
52
 
24
- // Show available models
25
- console.log(chalk.bold('\nAvailable models:'));
26
- console.log(chalk.dim('Claude:'));
27
- console.log(' claude-sonnet-4-5-20250929');
28
- console.log(' claude-opus-4-5-20250514');
29
- console.log(' claude-3-7-sonnet-20250219');
30
- console.log(' claude-haiku-4-5-20250514');
31
- console.log(chalk.dim('OpenAI (Codex):'));
32
- console.log(' gpt-5.1-codex-max');
33
- console.log(' gpt-5.1');
34
- console.log(' o1-preview');
35
- console.log(' o1-mini');
36
- console.log(chalk.dim('Google (Gemini):'));
37
- console.log(' gemini-3-pro-preview');
38
- console.log(' gemini-2-5-pro-exp-0827');
39
- console.log(' gemini-2-0-flash-exp');
53
+ printAvailableModels();
54
+
55
+ return;
56
+ }
40
57
 
58
+ if (!isValidModelId(modelId)) {
59
+ console.log(chalk.red(`✗ Modello non valido: ${modelId}`));
60
+ printAvailableModels();
61
+ process.exitCode = 1;
41
62
  return;
42
63
  }
43
64
 
@@ -42,9 +42,6 @@ const DEFAULT_CONFIG = {
42
42
  wake_lock: true,
43
43
  notifications: true,
44
44
  boot_start: false
45
- },
46
- preferences: {
47
- defaultModel: null // User's preferred default model (e.g., 'claude-sonnet-4-5-20250929')
48
45
  }
49
46
  };
50
47
 
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Shared model catalog for NexusCLI (backend + CLI)
3
+ * Keep this as the single source of truth for available engines/models.
4
+ */
5
+
6
+ /**
7
+ * Returns the available CLI tools and their models.
8
+ * Shape is consumed by API, frontend, and CLI commands.
9
+ */
10
+ function getCliTools() {
11
+ return {
12
+ // ============================================================
13
+ // CLAUDE - Anthropic Claude Code CLI
14
+ // ============================================================
15
+ 'claude': {
16
+ name: 'Claude Code',
17
+ icon: 'Terminal',
18
+ enabled: true,
19
+ endpoint: '/api/v1/chat',
20
+ thinkModes: ['think', 'no-think'],
21
+ defaultThinkMode: 'think',
22
+ models: [
23
+ // === Claude Opus 4.5 (Most Intelligent) ===
24
+ {
25
+ id: 'claude-opus-4-5-20251101',
26
+ name: 'claude-opus-4-5-20251101',
27
+ label: 'Opus 4.5',
28
+ description: '🧠 Most Intelligent',
29
+ category: 'claude'
30
+ },
31
+ // === Claude Sonnet 4.5 (Best Balance) ===
32
+ {
33
+ id: 'claude-sonnet-4-5-20250929',
34
+ name: 'claude-sonnet-4-5-20250929',
35
+ label: 'Sonnet 4.5',
36
+ description: '🧠 Extended Thinking (default)',
37
+ category: 'claude',
38
+ default: true
39
+ },
40
+ // === Claude Haiku 4.5 (Fastest) ===
41
+ {
42
+ id: 'claude-haiku-4-5-20251001',
43
+ name: 'claude-haiku-4-5-20251001',
44
+ label: 'Haiku 4.5',
45
+ description: '⚡ Fast & Efficient',
46
+ category: 'claude'
47
+ },
48
+ // === DeepSeek (Alternative Models) ===
49
+ {
50
+ id: 'deepseek-reasoner',
51
+ name: 'deepseek-reasoner',
52
+ label: 'DeepSeek Reasoner',
53
+ description: '🧠 Deep Reasoning',
54
+ category: 'claude'
55
+ },
56
+ {
57
+ id: 'deepseek-chat',
58
+ name: 'deepseek-chat',
59
+ label: 'DeepSeek Chat',
60
+ description: '💬 Fast Chat',
61
+ category: 'claude'
62
+ }
63
+ ]
64
+ },
65
+
66
+ // ============================================================
67
+ // CODEX - OpenAI Codex CLI
68
+ // ============================================================
69
+ 'codex': {
70
+ name: 'Codex',
71
+ icon: 'Code2',
72
+ enabled: true,
73
+ endpoint: '/api/v1/codex',
74
+ models: [
75
+ {
76
+ id: 'gpt-5.1-codex-max',
77
+ name: 'gpt-5.1-codex-max',
78
+ label: 'GPT-5.1 Codex Max',
79
+ description: '💎 Extra High reasoning (best)',
80
+ category: 'codex',
81
+ reasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
82
+ defaultReasoning: 'xhigh',
83
+ default: true
84
+ },
85
+ {
86
+ id: 'gpt-5.1-codex',
87
+ name: 'gpt-5.1-codex',
88
+ label: 'GPT-5.1 Codex',
89
+ description: '🧠 High reasoning',
90
+ category: 'codex',
91
+ reasoningEfforts: ['low', 'medium', 'high'],
92
+ defaultReasoning: 'high'
93
+ },
94
+ {
95
+ id: 'gpt-5.1-codex-mini',
96
+ name: 'gpt-5.1-codex-mini',
97
+ label: 'GPT-5.1 Codex Mini',
98
+ description: '⚡ Compact & Fast',
99
+ category: 'codex',
100
+ reasoningEfforts: ['medium', 'high'],
101
+ defaultReasoning: 'high'
102
+ },
103
+ {
104
+ id: 'gpt-5.1',
105
+ name: 'gpt-5.1',
106
+ label: 'GPT-5.1',
107
+ description: '🧠 General Purpose',
108
+ category: 'codex',
109
+ reasoningEfforts: ['low', 'medium', 'high'],
110
+ defaultReasoning: 'high'
111
+ }
112
+ ]
113
+ },
114
+
115
+ // ============================================================
116
+ // GEMINI - Google Gemini CLI
117
+ // ============================================================
118
+ 'gemini': {
119
+ name: 'Gemini',
120
+ icon: 'Sparkles',
121
+ enabled: true,
122
+ endpoint: '/api/v1/gemini',
123
+ models: [
124
+ {
125
+ id: 'gemini-3-pro-preview',
126
+ name: 'gemini-3-pro-preview',
127
+ label: 'Gemini 3 Pro',
128
+ description: '🚀 Latest Preview',
129
+ category: 'gemini',
130
+ default: true
131
+ }
132
+ ]
133
+ }
134
+ };
135
+ }
136
+
137
+ function getAllModels() {
138
+ return Object.values(getCliTools()).flatMap(cli => cli.models || []);
139
+ }
140
+
141
+ function isValidModelId(modelId) {
142
+ if (!modelId) return false;
143
+ return getAllModels().some(m => m.id === modelId);
144
+ }
145
+
146
+ function getDefaultModelId() {
147
+ const models = getAllModels();
148
+ const withDefault = models.find(m => m.default);
149
+ return withDefault ? withDefault.id : (models[0]?.id || null);
150
+ }
151
+
152
+ module.exports = {
153
+ getCliTools,
154
+ getAllModels,
155
+ isValidModelId,
156
+ getDefaultModelId
157
+ };
@@ -14,7 +14,7 @@ PORT=41800
14
14
  NODE_ENV=production
15
15
 
16
16
  # Workspace directory for CLI execution
17
- WORKSPACE_DIR=/var/www/cli.wellanet.dev
17
+ WORKSPACE_DIR=/home/user/myproject
18
18
 
19
19
  # Timeout for CLI commands (ms)
20
20
  DEFAULT_TIMEOUT=30000
@@ -6,6 +6,10 @@
6
6
  const express = require('express');
7
7
  const router = express.Router();
8
8
  const { getConfig } = require('../../config/manager');
9
+ const {
10
+ isValidModelId,
11
+ getDefaultModelId
12
+ } = require('../../config/models');
9
13
 
10
14
  /**
11
15
  * GET /api/v1/config
@@ -15,10 +19,21 @@ router.get('/', (req, res) => {
15
19
  try {
16
20
  const config = getConfig();
17
21
 
22
+ const preferred = config.preferences?.defaultModel || null;
23
+ let defaultModel = preferred;
24
+
25
+ // Sanitize preferred model; fallback to catalog default to avoid broken UI
26
+ if (defaultModel && !isValidModelId(defaultModel)) {
27
+ console.warn('[Config API] Invalid defaultModel in config:', defaultModel);
28
+ defaultModel = null;
29
+ }
30
+
31
+ if (!defaultModel) {
32
+ defaultModel = getDefaultModelId();
33
+ }
34
+
18
35
  // Return only preferences (not sensitive data like auth)
19
- const preferences = {
20
- defaultModel: config.preferences?.defaultModel || null
21
- };
36
+ const preferences = { defaultModel };
22
37
 
23
38
  res.json(preferences);
24
39
  } catch (error) {
@@ -1,5 +1,6 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
+ const { getCliTools } = require('../../config/models');
3
4
 
4
5
  /**
5
6
  * GET /api/v1/models
@@ -12,131 +13,7 @@ const router = express.Router();
12
13
  */
13
14
  router.get('/', (req, res) => {
14
15
  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
-
16
+ const cliTools = getCliTools();
140
17
  res.json(cliTools);
141
18
  } catch (error) {
142
19
  console.error('[Models] Error fetching models:', error);
@@ -151,6 +28,7 @@ router.get('/', (req, res) => {
151
28
  router.get('/:engine', (req, res) => {
152
29
  try {
153
30
  const { engine } = req.params;
31
+ const cliTools = getCliTools();
154
32
 
155
33
  // Normalize engine name
156
34
  let normalizedEngine = engine.toLowerCase();
@@ -158,37 +36,12 @@ router.get('/:engine', (req, res) => {
158
36
  if (normalizedEngine.includes('codex') || normalizedEngine.includes('openai')) normalizedEngine = 'codex';
159
37
  if (normalizedEngine.includes('gemini') || normalizedEngine.includes('google')) normalizedEngine = 'gemini';
160
38
 
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
39
  if (!cliTools[normalizedEngine]) {
188
40
  return res.status(404).json({ error: `Engine not found: ${engine}` });
189
41
  }
190
42
 
191
- res.json(cliTools[normalizedEngine]);
43
+ const { name, models } = cliTools[normalizedEngine];
44
+ res.json({ name, models });
192
45
  } catch (error) {
193
46
  console.error('[Models] Error fetching engine models:', error);
194
47
  res.status(500).json({ error: 'Failed to fetch models' });
@@ -66,6 +66,8 @@ router.get('/:id/messages', async (req, res) => {
66
66
 
67
67
  const { messages, pagination } = await cliLoader.loadMessagesFromCLI({
68
68
  sessionId,
69
+ threadId: session.session_path, // native thread id (Codex/Gemini)
70
+ sessionPath: session.session_path,
69
71
  engine: session.engine || 'claude-code',
70
72
  workspacePath: session.workspace_path,
71
73
  limit,
@@ -147,6 +149,8 @@ router.post('/:id/summarize', async (req, res) => {
147
149
  // Load messages from CLI history
148
150
  const { messages } = await cliLoader.loadMessagesFromCLI({
149
151
  sessionId,
152
+ threadId: session.session_path,
153
+ sessionPath: session.session_path,
150
154
  engine: session.engine || 'claude-code',
151
155
  workspacePath: session.workspace_path,
152
156
  limit,
@@ -224,7 +228,7 @@ router.delete('/:id', async (req, res) => {
224
228
 
225
229
  // Delete the original .jsonl file (SYNC DELETE)
226
230
  let fileDeleted = false;
227
- const sessionFile = getSessionFilePath(sessionId, session.engine, session.workspace_path);
231
+ const sessionFile = getSessionFilePath(sessionId, session.engine, session.workspace_path, session.session_path);
228
232
  if (sessionFile && fs.existsSync(sessionFile)) {
229
233
  try {
230
234
  fs.unlinkSync(sessionFile);
@@ -263,7 +267,7 @@ function pathToSlug(workspacePath) {
263
267
  /**
264
268
  * Helper: Get the filesystem path for a session file
265
269
  */
266
- function getSessionFilePath(sessionId, engine, workspacePath) {
270
+ function getSessionFilePath(sessionId, engine, workspacePath, sessionPath) {
267
271
  const normalizedEngine = engine?.toLowerCase().includes('claude') ? 'claude'
268
272
  : engine?.toLowerCase().includes('codex') ? 'codex'
269
273
  : engine?.toLowerCase().includes('gemini') ? 'gemini'
@@ -274,7 +278,12 @@ function getSessionFilePath(sessionId, engine, workspacePath) {
274
278
  const slug = pathToSlug(workspacePath);
275
279
  return path.join(SESSION_DIRS.claude, slug, `${sessionId}.jsonl`);
276
280
  case 'codex':
277
- return path.join(SESSION_DIRS.codex, `${sessionId}.jsonl`);
281
+ // Try native threadId first, then legacy sessionId
282
+ const nativeId = sessionPath || sessionId;
283
+ const baseDir = SESSION_DIRS.codex;
284
+ const flatPath = path.join(baseDir, `${nativeId}.jsonl`);
285
+ if (fs.existsSync(flatPath)) return flatPath;
286
+ return findCodexSessionFile(baseDir, nativeId);
278
287
  case 'gemini':
279
288
  return path.join(SESSION_DIRS.gemini, `${sessionId}.jsonl`);
280
289
  default:
@@ -282,4 +291,34 @@ function getSessionFilePath(sessionId, engine, workspacePath) {
282
291
  }
283
292
  }
284
293
 
294
+ function findCodexSessionFile(baseDir, threadId) {
295
+ if (!threadId || !fs.existsSync(baseDir)) return null;
296
+ try {
297
+ const years = fs.readdirSync(baseDir);
298
+ for (const year of years) {
299
+ const yearPath = path.join(baseDir, year);
300
+ if (!fs.statSync(yearPath).isDirectory()) continue;
301
+ const months = fs.readdirSync(yearPath);
302
+ for (const month of months) {
303
+ const monthPath = path.join(yearPath, month);
304
+ if (!fs.statSync(monthPath).isDirectory()) continue;
305
+ const days = fs.readdirSync(monthPath);
306
+ for (const day of days) {
307
+ const dayPath = path.join(monthPath, day);
308
+ if (!fs.statSync(dayPath).isDirectory()) continue;
309
+ const files = fs.readdirSync(dayPath);
310
+ for (const file of files) {
311
+ if (file.endsWith('.jsonl') && file.includes(threadId)) {
312
+ return path.join(dayPath, file);
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ } catch (err) {
319
+ console.warn(`[Sessions] Failed to search Codex session file: ${err.message}`);
320
+ }
321
+ return null;
322
+ }
323
+
285
324
  module.exports = router;
@@ -28,7 +28,6 @@ const wakeLockRouter = require('./routes/wake-lock');
28
28
  const uploadRouter = require('./routes/upload');
29
29
  const keysRouter = require('./routes/keys');
30
30
  const speechRouter = require('./routes/speech');
31
- const configRouter = require('./routes/config');
32
31
 
33
32
  const app = express();
34
33
  const PORT = process.env.PORT || 41800;
@@ -63,7 +62,6 @@ app.use(express.static(frontendDist));
63
62
  // Public routes
64
63
  app.use('/api/v1/auth', authRouter);
65
64
  app.use('/api/v1/models', modelsRouter);
66
- app.use('/api/v1/config', configRouter);
67
65
  app.use('/api/v1/workspace', workspaceRouter);
68
66
  app.use('/api/v1', wakeLockRouter); // Wake lock endpoints (public for app visibility handling)
69
67
  app.use('/api/v1/workspaces', authMiddleware, workspacesRouter);