npm-noxyai 1.0.21 → 1.0.22

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 (2) hide show
  1. package/index-noxyai.js +294 -56
  2. package/package.json +1 -1
package/index-noxyai.js CHANGED
@@ -10,6 +10,8 @@ const crypto = require('crypto');
10
10
  const BASE_URL = 'https://www.noxyai.com';
11
11
  const CONFIG_FILE = path.join(os.homedir(), '.noxyai.json');
12
12
  const CACHE_DIR = path.join(os.homedir(), '.noxyai_cache');
13
+ const VERSION = '2.0.0';
14
+
13
15
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
14
16
 
15
17
  // Ensure cache directory exists
@@ -18,6 +20,10 @@ if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true });
18
20
  const args = process.argv.slice(2);
19
21
  const command = args[0];
20
22
 
23
+ // ============================================
24
+ // CONFIG & SECURITY
25
+ // ============================================
26
+
21
27
  function loadConfig() {
22
28
  if (fs.existsSync(CONFIG_FILE)) {
23
29
  try {
@@ -72,6 +78,19 @@ function decryptToken(encryptedData) {
72
78
  }
73
79
  }
74
80
 
81
+ function getDeviceInfo() {
82
+ return {
83
+ platform: os.platform(),
84
+ name: os.hostname(),
85
+ userAgent: `NoxyAI-CLI/${VERSION} (${os.platform()}; ${os.hostname()})`,
86
+ fingerprint: crypto.createHash('sha256').update(os.platform() + os.hostname()).digest('hex')
87
+ };
88
+ }
89
+
90
+ // ============================================
91
+ // UI UTILITIES
92
+ // ============================================
93
+
75
94
  function printLogo() {
76
95
  console.log('\x1b[36m' + `
77
96
  ███╗ ██╗ ██████╗ ██╗ ██╗██╗ ██╗ █████╗ ██╗
@@ -81,6 +100,7 @@ function printLogo() {
81
100
  ██║ ╚████║╚██████╔╝██╔╝ ██╗ ██║ ██║ ██║██║
82
101
  ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝
83
102
  ` + '\x1b[0m');
103
+ console.log('\x1b[90m' + ` v${VERSION} - Secure Terminal Agent` + '\x1b[0m\n');
84
104
  }
85
105
 
86
106
  let spinnerInterval;
@@ -98,13 +118,26 @@ function stopSpinner() {
98
118
  process.stdout.write('\r\x1b[K\x1b[?25h');
99
119
  }
100
120
 
121
+ const askConfirm = (query) => new Promise(resolve => rl.question(query, resolve));
122
+
123
+ // ============================================
124
+ // AUTHENTICATION
125
+ // ============================================
126
+
101
127
  async function login() {
102
128
  printLogo();
103
129
  console.log('\x1b[33m🔐 Initializing secure device login...\x1b[0m\n');
130
+
131
+ const deviceInfo = getDeviceInfo();
132
+
104
133
  try {
105
134
  const initRes = await fetch(BASE_URL + '/api/cli/init', {
106
135
  method: 'POST',
107
- headers: { 'Content-Type': 'application/json' }
136
+ headers: {
137
+ 'Content-Type': 'application/json',
138
+ 'X-Client-Platform': deviceInfo.platform,
139
+ 'X-Client-Name': deviceInfo.name
140
+ }
108
141
  });
109
142
 
110
143
  if (!initRes.ok) throw new Error(`Server error: ${initRes.status}`);
@@ -115,17 +148,18 @@ async function login() {
115
148
  console.log(`\x1b[34m🌐 Login URL:\x1b[0m ${verificationUrl}`);
116
149
  console.log(`\n\x1b[90mThis code expires in ${Math.floor(expiresIn / 60)} minutes.\x1b[0m\n`);
117
150
 
151
+ // Copy to clipboard
118
152
  try {
119
153
  const platform = os.platform();
120
154
  if (platform === 'darwin') execSync(`echo "${userCode}" | pbcopy`);
121
155
  else if (platform === 'win32') execSync(`echo ${userCode} | clip`);
122
- else if (platform === 'linux') execSync(`echo "${userCode}" | xclip -selection clipboard 2>/dev/null || true`);
156
+ else if (platform === 'linux') execSync(`echo "${userCode}" | xclip -selection clipboard 2>/dev/null || echo "${userCode}" | wl-copy 2>/dev/null || true`);
123
157
  console.log('\x1b[32m✓ Code copied to clipboard!\x1b[0m');
124
158
  } catch (e) {}
125
159
 
126
160
  rl.question('\x1b[36mPress ENTER to open browser or type "q" to cancel:\x1b[0m ', (answer) => {
127
161
  if (answer.toLowerCase() === 'q') {
128
- console.log('\x1b[31mLogin cancelled.\x1b[0m');
162
+ console.log('\n\x1b[31mLogin cancelled.\x1b[0m');
129
163
  process.exit(0);
130
164
  }
131
165
 
@@ -151,7 +185,11 @@ async function login() {
151
185
  try {
152
186
  const pollRes = await fetch(BASE_URL + '/api/cli/poll', {
153
187
  method: 'POST',
154
- headers: { 'Content-Type': 'application/json' },
188
+ headers: {
189
+ 'Content-Type': 'application/json',
190
+ 'X-Client-Platform': deviceInfo.platform,
191
+ 'X-Client-Name': deviceInfo.name
192
+ },
155
193
  body: JSON.stringify({ deviceCode })
156
194
  });
157
195
 
@@ -164,11 +202,16 @@ async function login() {
164
202
  config.token = data.token;
165
203
  config.userEmail = data.email;
166
204
  config.userId = data.userId;
205
+ config.expiresAt = data.expiresAt;
206
+ config.deviceName = deviceInfo.name;
207
+ config.devicePlatform = deviceInfo.platform;
167
208
  saveConfig(config);
168
209
  console.clear();
169
210
  printLogo();
170
- console.log(`\x1b[32m✅ Successfully logged in as ${data.email}!\x1b[0m\n`);
171
- console.log(`\x1b[90mType 'noxyai' to start the interactive shell.\x1b[0m\n`);
211
+ console.log(`\x1b[32m✅ Successfully logged in as ${data.email}!\x1b[0m`);
212
+ console.log(`\x1b[90mSession expires: ${new Date(data.expiresAt).toLocaleDateString()}\x1b[0m`);
213
+ console.log(`\x1b[90mDevice: ${deviceInfo.name} (${deviceInfo.platform})\x1b[0m\n`);
214
+ console.log(`\x1b[90mType 'noxyai' to start.\x1b[0m\n`);
172
215
  process.exit(0);
173
216
  } else if (data.error) {
174
217
  clearInterval(pollInterval);
@@ -180,12 +223,133 @@ async function login() {
180
223
  }, (interval || 5) * 1000);
181
224
  } catch (error) {
182
225
  stopSpinner();
183
- console.error('\x1b[31m❌ Failed to connect:', error.message, '\x1b[0m');
226
+ console.error('\n\x1b[31m❌ Failed to connect:', error.message, '\x1b[0m');
184
227
  process.exit(1);
185
228
  }
186
229
  }
187
230
 
188
- const askConfirm = (query) => new Promise(resolve => rl.question(query, resolve));
231
+ async function logout() {
232
+ const config = loadConfig();
233
+
234
+ if (!config.token) {
235
+ console.log('\x1b[33mNot logged in.\x1b[0m\n');
236
+ process.exit(0);
237
+ }
238
+
239
+ try {
240
+ const res = await fetch(BASE_URL + '/api/cli/logout', {
241
+ method: 'POST',
242
+ headers: {
243
+ 'Authorization': 'Bearer ' + config.token,
244
+ 'Content-Type': 'application/json'
245
+ },
246
+ body: JSON.stringify({ allDevices: false })
247
+ });
248
+
249
+ config.token = null;
250
+ config.userEmail = null;
251
+ config.userId = null;
252
+ config.expiresAt = null;
253
+ saveConfig(config);
254
+
255
+ console.log('\n\x1b[32m✓ Successfully logged out.\x1b[0m\n');
256
+ } catch (error) {
257
+ config.token = null;
258
+ saveConfig(config);
259
+ console.log('\n\x1b[32m✓ Logged out locally.\x1b[0m\n');
260
+ }
261
+ process.exit(0);
262
+ }
263
+
264
+ async function logoutAll() {
265
+ const config = loadConfig();
266
+
267
+ if (!config.token) {
268
+ console.error('\n\x1b[31m❌ Not logged in.\x1b[0m\n');
269
+ return;
270
+ }
271
+
272
+ return new Promise((resolve) => {
273
+ rl.question('\x1b[33m⚠️ This will logout from ALL devices. Continue? [y/N]: \x1b[0m', async (answer) => {
274
+ if (answer.toLowerCase() !== 'y') {
275
+ console.log('\x1b[31mCancelled.\x1b[0m\n');
276
+ resolve();
277
+ return;
278
+ }
279
+
280
+ try {
281
+ const res = await fetch(BASE_URL + '/api/cli/logout', {
282
+ method: 'POST',
283
+ headers: {
284
+ 'Authorization': 'Bearer ' + config.token,
285
+ 'Content-Type': 'application/json'
286
+ },
287
+ body: JSON.stringify({ allDevices: true })
288
+ });
289
+
290
+ if (res.ok) {
291
+ const data = await res.json();
292
+ config.token = null;
293
+ config.userEmail = null;
294
+ saveConfig(config);
295
+ console.log(`\n\x1b[32m✓ ${data.message}\x1b[0m\n`);
296
+ process.exit(0);
297
+ } else {
298
+ console.error('\n\x1b[31m❌ Logout failed.\x1b[0m\n');
299
+ }
300
+ } catch (error) {
301
+ console.error('\n\x1b[31m❌ Error during logout.\x1b[0m\n');
302
+ }
303
+ resolve();
304
+ });
305
+ });
306
+ }
307
+
308
+ async function listSessions() {
309
+ const config = loadConfig();
310
+
311
+ if (!config.token) {
312
+ console.error('\n\x1b[31m❌ Not logged in.\x1b[0m\n');
313
+ return;
314
+ }
315
+
316
+ try {
317
+ const res = await fetch(BASE_URL + '/api/cli/sessions', {
318
+ headers: {
319
+ 'Authorization': 'Bearer ' + config.token
320
+ }
321
+ });
322
+
323
+ if (!res.ok) {
324
+ console.error('\n\x1b[31m❌ Failed to fetch sessions.\x1b[0m\n');
325
+ return;
326
+ }
327
+
328
+ const data = await res.json();
329
+
330
+ console.log('\n\x1b[36m📱 Active CLI Sessions:\x1b[0m');
331
+ console.log('\x1b[90m' + '─'.repeat(60) + '\x1b[0m');
332
+
333
+ data.sessions.forEach((session, i) => {
334
+ const isCurrent = session.current ? '\x1b[32m (current)\x1b[0m' : '';
335
+ const lastUsed = new Date(session.last_used_at).toLocaleString();
336
+
337
+ console.log(`${i + 1}. ${session.device_name} (${session.device_platform})${isCurrent}`);
338
+ console.log(` Last used: ${lastUsed}`);
339
+ console.log(` IP: ${session.ip_address || 'unknown'}`);
340
+ console.log('');
341
+ });
342
+
343
+ console.log('\x1b[90m' + '─'.repeat(60) + '\x1b[0m');
344
+ console.log('\x1b[33m/logout-all - Logout from all devices\x1b[0m\n');
345
+ } catch (error) {
346
+ console.error('\n\x1b[31m❌ Error fetching sessions.\x1b[0m\n');
347
+ }
348
+ }
349
+
350
+ // ============================================
351
+ // TERMINAL COMMANDS
352
+ // ============================================
189
353
 
190
354
  function runTerminalCommand(cmd, options = {}) {
191
355
  return new Promise((resolve, reject) => {
@@ -233,6 +397,10 @@ function runTerminalCommand(cmd, options = {}) {
233
397
  });
234
398
  }
235
399
 
400
+ // ============================================
401
+ // CONTEXT & CACHE
402
+ // ============================================
403
+
236
404
  function getLocalContext() {
237
405
  try {
238
406
  const cwd = process.cwd();
@@ -291,19 +459,21 @@ function cacheSearchResults(query, results) {
291
459
  }));
292
460
  }
293
461
 
294
- // REMOVED: validateSession function - session only expires on logout
462
+ // ============================================
463
+ // MAIN CHAT FUNCTION
464
+ // ============================================
295
465
 
296
466
  async function chat(prompt, depth = 0) {
297
467
  const config = loadConfig();
468
+ const deviceInfo = getDeviceInfo();
298
469
 
299
- // SIMPLE CHECK: Only check if token exists, no validation endpoint
300
470
  if (!config.token) {
301
- console.error('\x1b[31m❌ Not logged in. Run "noxyai login" first\x1b[0m');
471
+ console.error('\n\x1b[31m❌ Not logged in. Run "noxyai login" first\x1b[0m');
302
472
  return;
303
473
  }
304
474
 
305
475
  if (depth > 8) {
306
- console.error('\n\x1b[31m❌ Maximum recursion depth reached. Breaking loop.\x1b[0m');
476
+ console.error('\n\x1b[31m❌ Maximum recursion depth reached.\x1b[0m');
307
477
  return;
308
478
  }
309
479
 
@@ -319,8 +489,10 @@ async function chat(prompt, depth = 0) {
319
489
  headers: {
320
490
  'Content-Type': 'application/json',
321
491
  'Authorization': 'Bearer ' + config.token,
322
- 'X-Client-Version': '1.0.0',
323
- 'X-Client-Platform': os.platform()
492
+ 'X-Client-Version': VERSION,
493
+ 'X-Client-Platform': deviceInfo.platform,
494
+ 'X-Client-Name': deviceInfo.name,
495
+ 'User-Agent': deviceInfo.userAgent
324
496
  },
325
497
  body: JSON.stringify({
326
498
  prompt: enhancedPrompt,
@@ -336,21 +508,27 @@ async function chat(prompt, depth = 0) {
336
508
 
337
509
  stopSpinner();
338
510
 
511
+ // Handle token rotation from header
512
+ const newToken = res.headers.get('X-New-Token');
513
+ if (newToken) {
514
+ config.token = newToken;
515
+ saveConfig(config);
516
+ console.log('\x1b[90m[Security] Token automatically rotated\x1b[0m');
517
+ }
518
+
339
519
  if (!res.ok) {
340
520
  const errorText = await res.text();
341
521
 
342
- // Only logout on explicit 401 from backend (token actually invalid)
343
522
  if (res.status === 401) {
344
- console.error('\x1b[31m❌ Invalid session. Please login again.\x1b[0m');
523
+ console.error('\n\x1b[31m❌ Session invalid or expired.\x1b[0m');
345
524
  console.error('\x1b[33mRun:\x1b[0m \x1b[36mnoxyai login\x1b[0m\n');
346
525
  config.token = null;
347
- config.userEmail = null;
348
526
  saveConfig(config);
349
527
  return;
350
528
  }
351
529
 
352
530
  if (res.status === 402) {
353
- console.error('\x1b[31m❌ Insufficient credits.\x1b[0m');
531
+ console.error('\n\x1b[31m❌ Insufficient credits.\x1b[0m');
354
532
  console.error('\x1b[33mVisit:\x1b[0m \x1b[36mhttps://noxyai.com/pricing\x1b[0m\n');
355
533
  return;
356
534
  }
@@ -372,9 +550,9 @@ async function chat(prompt, depth = 0) {
372
550
 
373
551
  let isFirstReasoning = true;
374
552
  let reasoningClosed = false;
375
- let actionsPerformed = [];
376
553
  let totalCost = 0;
377
554
  let totalTokens = 0;
555
+ let actionsPerformed = 0;
378
556
 
379
557
  while (true) {
380
558
  const { done, value } = await reader.read();
@@ -390,6 +568,12 @@ async function chat(prompt, depth = 0) {
390
568
  try {
391
569
  const parsed = JSON.parse(data);
392
570
 
571
+ // Handle token rotation in stream
572
+ if (parsed.token_rotated) {
573
+ config.token = parsed.token_rotated;
574
+ saveConfig(config);
575
+ }
576
+
393
577
  if (parsed.isReasoning) {
394
578
  if (isFirstReasoning) {
395
579
  process.stdout.write('\n\x1b[35m┌── 🧠 Deep Reasoning ─────────────────────\x1b[0m\n\x1b[31m│ \x1b[0m');
@@ -440,6 +624,7 @@ async function chat(prompt, depth = 0) {
440
624
  console.log('\x1b[32m[Agent] Executing actions...\x1b[0m');
441
625
  let agentFeedback = "";
442
626
 
627
+ // Tool: Notifications
443
628
  const notifyRegex = /<notify>([\s\S]*?)<\/notify>/g;
444
629
  let match;
445
630
  while ((match = notifyRegex.exec(fullResponse)) !== null) {
@@ -456,6 +641,7 @@ async function chat(prompt, depth = 0) {
456
641
  }
457
642
  }
458
643
 
644
+ // Tool: Read Files
459
645
  const readRegex = /<read>([\s\S]*?)<\/read>/g;
460
646
  while ((match = readRegex.exec(fullResponse)) !== null) {
461
647
  const filePath = match[1].trim();
@@ -478,6 +664,7 @@ async function chat(prompt, depth = 0) {
478
664
  }
479
665
  }
480
666
 
667
+ // Tool: Web Search
481
668
  const searchRegex = /<search>([\s\S]*?)<\/search>/g;
482
669
  while ((match = searchRegex.exec(fullResponse)) !== null) {
483
670
  const query = match[1].trim();
@@ -530,6 +717,7 @@ async function chat(prompt, depth = 0) {
530
717
  }
531
718
  }
532
719
 
720
+ // Tool: Write Files
533
721
  const fileRegex = /<file path="([^"]+)">([\s\S]*?)<\/file>/g;
534
722
  while ((match = fileRegex.exec(fullResponse)) !== null) {
535
723
  const filePath = match[1], content = match[2];
@@ -564,6 +752,7 @@ async function chat(prompt, depth = 0) {
564
752
  }
565
753
  }
566
754
 
755
+ // Tool: Execute Commands
567
756
  const execRegex = /<execute>([\s\S]*?)<\/execute>/g;
568
757
  const commands = [];
569
758
  while ((match = execRegex.exec(fullResponse)) !== null) {
@@ -643,23 +832,29 @@ async function chat(prompt, depth = 0) {
643
832
  }
644
833
  }
645
834
 
835
+ // ============================================
836
+ // INTERACTIVE MODE
837
+ // ============================================
838
+
646
839
  function startInteractiveMode() {
647
840
  const config = loadConfig();
648
841
  printLogo();
649
842
 
650
843
  if (!config.token) {
651
- console.log('\n\x1b[33mWelcome to NoxyAI!\x1b[0m');
844
+ console.log('\x1b[33mWelcome to NoxyAI!\x1b[0m');
652
845
  console.log('\x1b[36mTo get started, run:\x1b[0m \x1b[32mnoxyai login\x1b[0m\n');
653
846
  process.exit(0);
654
847
  }
655
848
 
656
849
  console.log(`\x1b[33mWelcome back${config.userEmail ? ', ' + config.userEmail : ''}!\x1b[0m`);
657
- console.log(`\n\x1b[90m${'─'.repeat(40)}\x1b[0m`);
658
- console.log(`Model: \x1b[32m${config.model}\x1b[0m`);
659
- console.log(`Agent Mode: ${config.agentMode ? '\x1b[32m● ON\x1b[0m' : '\x1b[31m○ OFF\x1b[0m'}`);
660
- console.log(`Safe Mode: ${config.safeMode ? '\x1b[32m● ON\x1b[0m' : '\x1b[33m○ OFF\x1b[0m'}`);
661
- console.log(`\x1b[90m${'─'.repeat(40)}\x1b[0m`);
662
- console.log(`\nCommands: \x1b[36m/model\x1b[0m \x1b[36m/agent\x1b[0m \x1b[36m/safe\x1b[0m \x1b[36m/clear\x1b[0m \x1b[36m/status\x1b[0m \x1b[36m/exit\x1b[0m`);
850
+ console.log(`\n\x1b[90m${'─'.repeat(45)}\x1b[0m`);
851
+ console.log(`Model: \x1b[32m${config.model}\x1b[0m | Agent: ${config.agentMode ? '\x1b[32m● ON\x1b[0m' : '\x1b[31m○ OFF\x1b[0m'} | Safe: ${config.safeMode ? '\x1b[32m● ON\x1b[0m' : '\x1b[33m○ OFF\x1b[0m'}`);
852
+ console.log(`Device: ${config.deviceName || 'unknown'} (${config.devicePlatform || 'unknown'})`);
853
+ if (config.expiresAt) {
854
+ console.log(`Expires: ${new Date(config.expiresAt).toLocaleDateString()}`);
855
+ }
856
+ console.log(`\x1b[90m${'─'.repeat(45)}\x1b[0m`);
857
+ console.log(`\nCommands: \x1b[36m/model\x1b[0m \x1b[36m/agent\x1b[0m \x1b[36m/safe\x1b[0m \x1b[36m/clear\x1b[0m \x1b[36m/status\x1b[0m \x1b[36m/sessions\x1b[0m \x1b[36m/exit\x1b[0m`);
663
858
  console.log(`Type your question or task below.\n`);
664
859
 
665
860
  function ask() {
@@ -678,30 +873,39 @@ function startInteractiveMode() {
678
873
  const c = loadConfig();
679
874
  c.agentMode = !c.agentMode;
680
875
  saveConfig(c);
681
- console.log(`\n\x1b[33mAgent Mode: ${c.agentMode ? '\x1b[32m● ON\x1b[0m' : '\x1b[31m○ OFF\x1b[0m'}\n`);
876
+ console.log(`\n\x1b[33mAgent Mode: ${c.agentMode ? '\x1b[32m● ON (File/Web/Execute)\x1b[0m' : '\x1b[31m○ OFF (Chat only)\x1b[0m'}\n`);
682
877
  ask();
683
878
  } else if (lower === '/safe') {
684
879
  const c = loadConfig();
685
880
  c.safeMode = !c.safeMode;
686
881
  saveConfig(c);
687
- console.log(`\n\x1b[33mSafe Mode: ${c.safeMode ? '\x1b[32m● ON\x1b[0m' : '\x1b[33m○ OFF\x1b[0m'}\n`);
882
+ console.log(`\n\x1b[33mSafe Mode: ${c.safeMode ? '\x1b[32m● ON (Confirm commands)\x1b[0m' : '\x1b[33m○ OFF (Auto-execute)\x1b[0m'}\n`);
688
883
  ask();
689
884
  } else if (lower === '/status') {
690
885
  const c = loadConfig();
691
- console.log(`\n\x1b[90m${'─'.repeat(40)}\x1b[0m`);
886
+ console.log(`\n\x1b[90m${'─'.repeat(45)}\x1b[0m`);
692
887
  console.log(`\x1b[36mSession Status:\x1b[0m`);
693
888
  console.log(` • User: ${c.userEmail || 'Unknown'}`);
694
889
  console.log(` • Model: ${c.model}`);
695
890
  console.log(` • Agent: ${c.agentMode ? 'ON' : 'OFF'}`);
696
891
  console.log(` • Safe: ${c.safeMode ? 'ON' : 'OFF'}`);
697
892
  console.log(` • CWD: ${process.cwd()}`);
698
- console.log(`\x1b[90m${''.repeat(40)}\x1b[0m\n`);
893
+ console.log(` • Device: ${c.deviceName || 'unknown'} (${c.devicePlatform || 'unknown'})`);
894
+ console.log(` • Expires: ${c.expiresAt ? new Date(c.expiresAt).toLocaleDateString() : 'N/A'}`);
895
+ console.log(`\x1b[90m${'─'.repeat(45)}\x1b[0m\n`);
896
+ ask();
897
+ } else if (lower === '/sessions') {
898
+ await listSessions();
899
+ ask();
900
+ } else if (lower === '/logout-all') {
901
+ await logoutAll();
902
+ if (!loadConfig().token) process.exit(0);
699
903
  ask();
700
904
  } else if (lower === '/model') {
701
905
  console.log('\n\x1b[33mSelect AI Model:\x1b[0m');
702
- console.log(' \x1b[36m1)\x1b[0m Auto (Llama 3.3 70B)');
703
- console.log(' \x1b[36m2)\x1b[0m Qwen3 Next 80B');
704
- console.log(' \x1b[36m3)\x1b[0m Mistral Mamba Codestral 7B');
906
+ console.log(' \x1b[36m1)\x1b[0m Auto (Llama 3.3 70B) - Fast & Capable');
907
+ console.log(' \x1b[36m2)\x1b[0m Qwen3 Next 80B - Deep Reasoning');
908
+ console.log(' \x1b[36m3)\x1b[0m Mistral Mamba Codestral 7B - Code Optimized');
705
909
 
706
910
  rl.question('\n\x1b[36mChoice (1-3):\x1b[0m ', (choice) => {
707
911
  let selected = 'auto';
@@ -728,43 +932,66 @@ function startInteractiveMode() {
728
932
  ask();
729
933
  }
730
934
 
731
- // CLI Router
935
+ // ============================================
936
+ // CLI ROUTER
937
+ // ============================================
938
+
732
939
  if (command === 'login') {
733
940
  login();
734
- } else if (command === 'logout') {
735
- const c = loadConfig();
736
- c.token = null;
737
- c.userEmail = null;
738
- saveConfig(c);
739
- console.log('\n\x1b[32m✓ Successfully logged out.\x1b[0m\n');
740
- process.exit(0);
941
+ } else if (command === 'logout') {
942
+ logout();
741
943
  } else if (command === 'help' || command === '--help' || command === '-h') {
742
944
  printLogo();
743
945
  console.log(`
744
- \x1b[33mNoxyAI - Autonomous Terminal Agent\x1b[0m
946
+ \x1b[33mNoxyAI - Secure Autonomous Terminal Agent\x1b[0m
745
947
 
746
948
  \x1b[36mUsage:\x1b[0m
747
- noxyai Interactive mode
748
- noxyai login Authenticate
749
- noxyai logout Remove auth
750
- noxyai help Show help
751
-
752
- \x1b[36mCommands:\x1b[0m
753
- /model Switch model
754
- /agent Toggle agent mode
755
- /safe Toggle safe mode
756
- /status Show status
757
- /clear Clear screen
758
- /exit Exit
949
+ noxyai Start interactive mode
950
+ noxyai login Authenticate with device code
951
+ noxyai logout Logout from this device
952
+ noxyai help Show this help message
953
+ noxyai version Show version
954
+
955
+ \x1b[36mInteractive Commands:\x1b[0m
956
+ /model Switch AI model (Auto/Qwen3/Mamba)
957
+ /agent Toggle agent mode (file/web/execute)
958
+ /safe Toggle safe mode (command confirmation)
959
+ /status Show current session status
960
+ /sessions List all active CLI sessions
961
+ /logout-all Logout from ALL devices
962
+ /clear Clear the screen
963
+ /exit Exit the program
964
+
965
+ \x1b[36mExamples:\x1b[0m
966
+ $ noxyai login
967
+ $ noxyai
968
+ > Create a React component
969
+ > Search for Python async best practices
970
+ > Optimize this Dockerfile
971
+
972
+ \x1b[36mSecurity Features:\x1b[0m
973
+ • Device fingerprint binding
974
+ • Automatic token rotation (7 days)
975
+ • 30-day session expiration
976
+ • Local token encryption
977
+ • Rate limiting protection
978
+
979
+ \x1b[90mDocumentation: https://noxyai.com/docs\x1b[0m
759
980
  `);
760
981
  process.exit(0);
761
982
  } else if (command === 'version' || command === '--version' || command === '-v') {
762
- console.log('NoxyAI CLI v1.0.2');
983
+ console.log(`NoxyAI CLI v${VERSION}`);
984
+ console.log(`Platform: ${os.platform()} ${os.release()}`);
985
+ console.log(`Node: ${process.version}`);
763
986
  process.exit(0);
764
987
  } else if (!command) {
765
988
  startInteractiveMode();
989
+ } else if (command.startsWith('-')) {
990
+ console.error(`\x1b[31m❌ Unknown option: ${command}\x1b[0m`);
991
+ console.error(`\x1b[90mRun 'noxyai help' for usage information.\x1b[0m\n`);
992
+ process.exit(1);
766
993
  } else {
767
- // Direct query
994
+ // Direct query mode: noxyai "your question here"
768
995
  const query = args.join(' ');
769
996
  const config = loadConfig();
770
997
 
@@ -775,3 +1002,14 @@ if (command === 'login') {
775
1002
 
776
1003
  chat(query).then(() => process.exit(0));
777
1004
  }
1005
+
1006
+ // Handle Ctrl+C gracefully
1007
+ process.on('SIGINT', () => {
1008
+ console.log('\n\n\x1b[36mGoodbye! 👋\x1b[0m\n');
1009
+ process.exit(0);
1010
+ });
1011
+
1012
+ process.on('SIGTERM', () => {
1013
+ console.log('\n\n\x1b[36mSession terminated.\x1b[0m\n');
1014
+ process.exit(0);
1015
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npm-noxyai",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "CLI for NoxyAI",
5
5
  "main": "index-noxyai.js",
6
6
  "bin": {