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.
- package/index-noxyai.js +294 -56
- 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: {
|
|
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: {
|
|
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
|
|
171
|
-
console.log(`\x1b[
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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':
|
|
323
|
-
'X-Client-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❌
|
|
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('\
|
|
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(
|
|
658
|
-
console.log(`Model: \x1b[32m${config.model}\x1b[0m`);
|
|
659
|
-
console.log(`
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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(
|
|
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(
|
|
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
|
-
//
|
|
935
|
+
// ============================================
|
|
936
|
+
// CLI ROUTER
|
|
937
|
+
// ============================================
|
|
938
|
+
|
|
732
939
|
if (command === 'login') {
|
|
733
940
|
login();
|
|
734
|
-
} else if (command === 'logout') {
|
|
735
|
-
|
|
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
|
|
748
|
-
noxyai login Authenticate
|
|
749
|
-
noxyai logout
|
|
750
|
-
noxyai help Show help
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
/
|
|
755
|
-
/
|
|
756
|
-
/
|
|
757
|
-
/
|
|
758
|
-
/
|
|
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(
|
|
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
|
+
});
|