npm-noxyai 1.0.16 → 1.0.19
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 +641 -117
- package/package.json +1 -1
package/index-noxyai.js
CHANGED
|
@@ -3,22 +3,82 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const { spawn } = require('child_process');
|
|
6
|
+
const { spawn, execSync } = require('child_process');
|
|
7
7
|
const readline = require('readline');
|
|
8
|
+
const crypto = require('crypto');
|
|
8
9
|
|
|
9
10
|
const BASE_URL = 'https://www.noxyai.com';
|
|
10
11
|
const CONFIG_FILE = path.join(os.homedir(), '.noxyai.json');
|
|
12
|
+
const SESSION_FILE = path.join(os.homedir(), '.noxyai_session.json');
|
|
13
|
+
const CACHE_DIR = path.join(os.homedir(), '.noxyai_cache');
|
|
14
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
+
|
|
16
|
+
// Ensure cache directory exists
|
|
17
|
+
if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
11
18
|
|
|
12
19
|
const args = process.argv.slice(2);
|
|
13
20
|
const command = args[0];
|
|
14
21
|
|
|
22
|
+
// Rate limiting store (in-memory, resets on CLI restart)
|
|
23
|
+
const rateLimitStore = new Map();
|
|
24
|
+
|
|
15
25
|
function loadConfig() {
|
|
16
26
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
17
|
-
try {
|
|
27
|
+
try {
|
|
28
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
29
|
+
// Decrypt token if it was encrypted
|
|
30
|
+
if (config.token && config.token.startsWith('enc:')) {
|
|
31
|
+
config.token = decryptToken(config.token.substring(4));
|
|
32
|
+
}
|
|
33
|
+
return config;
|
|
34
|
+
} catch(e) {
|
|
35
|
+
console.error('Config corruption detected, resetting...');
|
|
36
|
+
return { token: null, model: 'auto', agentMode: false, autoApprove: false, safeMode: true };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { token: null, model: 'auto', agentMode: false, autoApprove: false, safeMode: true };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function saveConfig(config) {
|
|
43
|
+
// Encrypt token for security
|
|
44
|
+
const configToSave = { ...config };
|
|
45
|
+
if (configToSave.token && !configToSave.token.startsWith('enc:')) {
|
|
46
|
+
configToSave.token = 'enc:' + encryptToken(configToSave.token);
|
|
47
|
+
}
|
|
48
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(configToSave, null, 2));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Simple token encryption using machine-specific key
|
|
52
|
+
function getMachineKey() {
|
|
53
|
+
const networkInterfaces = os.networkInterfaces();
|
|
54
|
+
const mac = Object.values(networkInterfaces)
|
|
55
|
+
.flat()
|
|
56
|
+
.find(i => !i.internal && i.mac !== '00:00:00:00:00:00')?.mac || os.hostname();
|
|
57
|
+
return crypto.createHash('sha256').update(mac + os.userInfo().username).digest('hex');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function encryptToken(token) {
|
|
61
|
+
const key = getMachineKey();
|
|
62
|
+
const iv = crypto.randomBytes(16);
|
|
63
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv);
|
|
64
|
+
let encrypted = cipher.update(token, 'utf8', 'hex');
|
|
65
|
+
encrypted += cipher.final('hex');
|
|
66
|
+
return iv.toString('hex') + ':' + encrypted;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function decryptToken(encryptedData) {
|
|
70
|
+
try {
|
|
71
|
+
const key = getMachineKey();
|
|
72
|
+
const [ivHex, encrypted] = encryptedData.split(':');
|
|
73
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
74
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv);
|
|
75
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
76
|
+
decrypted += decipher.final('utf8');
|
|
77
|
+
return decrypted;
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return null;
|
|
18
80
|
}
|
|
19
|
-
return { token: null, model: 'auto', agentMode: false };
|
|
20
81
|
}
|
|
21
|
-
function saveConfig(config) { fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); }
|
|
22
82
|
|
|
23
83
|
function printLogo() {
|
|
24
84
|
console.log('\x1b[36m' + `
|
|
@@ -46,232 +106,696 @@ function stopSpinner() {
|
|
|
46
106
|
process.stdout.write('\r\x1b[K\x1b[?25h');
|
|
47
107
|
}
|
|
48
108
|
|
|
109
|
+
// Enhanced login with device flow
|
|
49
110
|
async function login() {
|
|
50
111
|
printLogo();
|
|
51
|
-
console.log('Initializing secure login...\n');
|
|
112
|
+
console.log('\x1b[33m🔐 Initializing secure device login...\x1b[0m\n');
|
|
52
113
|
try {
|
|
53
|
-
const initRes = await fetch(BASE_URL + '/api/cli/init', {
|
|
54
|
-
|
|
55
|
-
|
|
114
|
+
const initRes = await fetch(BASE_URL + '/api/cli/init', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' }
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (!initRes.ok) throw new Error(`Server error: ${initRes.status}`);
|
|
120
|
+
|
|
121
|
+
const { deviceCode, verificationUrl, userCode, expiresIn, interval } = await initRes.json();
|
|
122
|
+
|
|
123
|
+
console.log(`\x1b[32m📱 Verification Code:\x1b[0m \x1b[33m${userCode}\x1b[0m`);
|
|
124
|
+
console.log(`\x1b[34m🌐 Login URL:\x1b[0m ${verificationUrl}`);
|
|
125
|
+
console.log(`\n\x1b[90mThis code expires in ${Math.floor(expiresIn / 60)} minutes.\x1b[0m\n`);
|
|
56
126
|
|
|
57
|
-
|
|
58
|
-
|
|
127
|
+
// Try to copy code to clipboard
|
|
128
|
+
try {
|
|
129
|
+
const platform = os.platform();
|
|
130
|
+
if (platform === 'darwin') execSync(`echo "${userCode}" | pbcopy`);
|
|
131
|
+
else if (platform === 'win32') execSync(`echo ${userCode} | clip`);
|
|
132
|
+
else if (platform === 'linux' && execSync('which xclip').toString())
|
|
133
|
+
execSync(`echo "${userCode}" | xclip -selection clipboard`);
|
|
134
|
+
console.log('\x1b[32m✓ Code copied to clipboard!\x1b[0m');
|
|
135
|
+
} catch (e) {}
|
|
136
|
+
|
|
137
|
+
rl.question('\x1b[36mPress ENTER to open browser or type "q" to cancel:\x1b[0m ', (answer) => {
|
|
138
|
+
if (answer.toLowerCase() === 'q') {
|
|
139
|
+
console.log('\x1b[31mLogin cancelled.\x1b[0m');
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
|
|
59
143
|
const platform = os.platform();
|
|
60
144
|
if (platform === 'android') spawn('termux-open-url', [verificationUrl], { stdio: 'ignore' });
|
|
145
|
+
else if (platform === 'darwin') spawn('open', [verificationUrl], { stdio: 'ignore' });
|
|
146
|
+
else if (platform === 'win32') spawn('start', [verificationUrl], { shell: true, stdio: 'ignore' });
|
|
61
147
|
else spawn('xdg-open', [verificationUrl], { stdio: 'ignore' });
|
|
62
|
-
|
|
148
|
+
|
|
63
149
|
startSpinner('Waiting for authentication...');
|
|
64
150
|
});
|
|
65
151
|
|
|
152
|
+
const startTime = Date.now();
|
|
153
|
+
const maxTime = expiresIn * 1000;
|
|
66
154
|
const pollInterval = setInterval(async () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
155
|
+
if (Date.now() - startTime > maxTime) {
|
|
156
|
+
clearInterval(pollInterval);
|
|
157
|
+
stopSpinner();
|
|
158
|
+
console.log('\x1b[31m❌ Login timeout. Please try again.\x1b[0m');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const pollRes = await fetch(BASE_URL + '/api/cli/poll', {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: { 'Content-Type': 'application/json' },
|
|
166
|
+
body: JSON.stringify({ deviceCode })
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const data = await pollRes.json();
|
|
170
|
+
|
|
171
|
+
if (data.status === 'success') {
|
|
172
|
+
clearInterval(pollInterval);
|
|
173
|
+
stopSpinner();
|
|
174
|
+
const config = loadConfig();
|
|
175
|
+
config.token = data.token;
|
|
176
|
+
config.userEmail = data.email;
|
|
177
|
+
config.userId = data.userId;
|
|
178
|
+
saveConfig(config);
|
|
179
|
+
console.clear();
|
|
180
|
+
printLogo();
|
|
181
|
+
console.log(`\x1b[32m✅ Successfully logged in as ${data.email}!\x1b[0m\n`);
|
|
182
|
+
console.log(`\x1b[90mType 'noxyai' to start the interactive shell.\x1b[0m\n`);
|
|
183
|
+
process.exit(0);
|
|
184
|
+
} else if (data.status === 'pending') {
|
|
185
|
+
// Still waiting
|
|
186
|
+
} else if (data.error) {
|
|
187
|
+
clearInterval(pollInterval);
|
|
188
|
+
stopSpinner();
|
|
189
|
+
console.error(`\n\x1b[31m❌ Login failed: ${data.error}\x1b[0m`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
// Network errors shouldn't break the polling
|
|
77
194
|
}
|
|
78
|
-
}, interval * 1000);
|
|
79
|
-
} catch (error) {
|
|
195
|
+
}, (interval || 5) * 1000);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
stopSpinner();
|
|
198
|
+
console.error('\x1b[31m❌ Failed to connect:', error.message, '\x1b[0m');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
80
201
|
}
|
|
81
202
|
|
|
82
|
-
|
|
203
|
+
const askConfirm = (query) => new Promise(resolve => rl.question(query, resolve));
|
|
204
|
+
|
|
205
|
+
function runTerminalCommand(cmd, options = {}) {
|
|
83
206
|
return new Promise((resolve, reject) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
207
|
+
const { silent = false, background = false } = options;
|
|
208
|
+
|
|
209
|
+
if (!silent) {
|
|
210
|
+
console.log(`\n\x1b[33m⚡ Executing:\x1b[0m \x1b[36m${cmd}\x1b[0m\n`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const isServer = cmd.includes('dev') || cmd.includes('serve') ||
|
|
214
|
+
cmd.includes('host') || cmd.includes('start') ||
|
|
215
|
+
cmd.includes('watch');
|
|
216
|
+
|
|
217
|
+
const child = spawn(cmd, {
|
|
218
|
+
shell: true,
|
|
219
|
+
stdio: silent ? 'pipe' : 'inherit',
|
|
220
|
+
detached: background
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (background) {
|
|
224
|
+
child.unref();
|
|
225
|
+
resolve({ pid: child.pid, background: true });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (isServer && !silent) {
|
|
88
230
|
console.log(`\x1b[36m[i] Server detected. Running in foreground. Press Ctrl+C to stop.\x1b[0m`);
|
|
89
|
-
setTimeout(() => resolve(), 2500);
|
|
231
|
+
setTimeout(() => resolve({ server: true }), 2500);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let stdout = '', stderr = '';
|
|
235
|
+
if (silent) {
|
|
236
|
+
child.stdout?.on('data', (data) => stdout += data.toString());
|
|
237
|
+
child.stderr?.on('data', (data) => stderr += data.toString());
|
|
90
238
|
}
|
|
91
|
-
|
|
92
|
-
child.on('
|
|
239
|
+
|
|
240
|
+
child.on('close', (code) => {
|
|
241
|
+
if (!isServer || silent) {
|
|
242
|
+
if (code !== 0) reject(new Error(stderr || `Exit code ${code}`));
|
|
243
|
+
else resolve({ code, stdout, stderr });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
child.on('error', (error) => reject(error));
|
|
93
248
|
});
|
|
94
249
|
}
|
|
95
250
|
|
|
251
|
+
// Enhanced context gathering
|
|
96
252
|
function getLocalContext() {
|
|
97
253
|
try {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
254
|
+
const cwd = process.cwd();
|
|
255
|
+
const files = fs.readdirSync(cwd)
|
|
256
|
+
.filter(f => !f.startsWith('.') && f !== 'node_modules' && f !== '__pycache__')
|
|
257
|
+
.slice(0, 50);
|
|
258
|
+
|
|
259
|
+
const packageJson = files.includes('package.json')
|
|
260
|
+
? JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'))
|
|
261
|
+
: null;
|
|
262
|
+
|
|
263
|
+
const gitBranch = (() => {
|
|
264
|
+
try {
|
|
265
|
+
return execSync('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
266
|
+
} catch (e) { return null; }
|
|
267
|
+
})();
|
|
268
|
+
|
|
269
|
+
return `\n[SYSTEM CONTEXT]
|
|
270
|
+
Directory: ${cwd}
|
|
271
|
+
Files: ${files.join(', ')}
|
|
272
|
+
${packageJson ? `Project: ${packageJson.name || 'unnamed'} v${packageJson.version || '0.0.0'}` : ''}
|
|
273
|
+
${gitBranch ? `Git Branch: ${gitBranch}` : ''}
|
|
274
|
+
OS: ${os.platform()} ${os.release()}
|
|
275
|
+
Shell: ${process.env.SHELL || 'unknown'}
|
|
276
|
+
[/SYSTEM CONTEXT]\n`;
|
|
277
|
+
} catch (e) {
|
|
278
|
+
return `\n[SYSTEM CONTEXT] Directory: ${process.cwd()}\n[/SYSTEM CONTEXT]\n`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Cache management for search results
|
|
283
|
+
function getCachedSearch(query) {
|
|
284
|
+
const cacheFile = path.join(CACHE_DIR, `search_${crypto.createHash('md5').update(query).digest('hex')}.json`);
|
|
285
|
+
if (fs.existsSync(cacheFile)) {
|
|
286
|
+
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
287
|
+
if (Date.now() - cache.timestamp < 3600000) { // 1 hour cache
|
|
288
|
+
return cache.results;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function cacheSearchResults(query, results) {
|
|
295
|
+
const cacheFile = path.join(CACHE_DIR, `search_${crypto.createHash('md5').update(query).digest('hex')}.json`);
|
|
296
|
+
fs.writeFileSync(cacheFile, JSON.stringify({
|
|
297
|
+
timestamp: Date.now(),
|
|
298
|
+
query,
|
|
299
|
+
results
|
|
300
|
+
}));
|
|
101
301
|
}
|
|
102
302
|
|
|
103
303
|
async function chat(prompt, depth = 0) {
|
|
104
304
|
const config = loadConfig();
|
|
105
|
-
if (!config.token) {
|
|
106
|
-
|
|
305
|
+
if (!config.token) {
|
|
306
|
+
console.error('\x1b[31m❌ Unauthorized: Run "noxyai login" first\x1b[0m');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (depth > 8) {
|
|
311
|
+
console.error('\n\x1b[31m❌ Maximum recursion depth reached. Breaking loop.\x1b[0m');
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
107
314
|
|
|
108
|
-
const enhancedPrompt = (depth === 0 && config.agentMode)
|
|
109
|
-
|
|
315
|
+
const enhancedPrompt = (depth === 0 && config.agentMode)
|
|
316
|
+
? getLocalContext() + prompt
|
|
317
|
+
: prompt;
|
|
318
|
+
|
|
319
|
+
startSpinner(depth === 0 ? 'NoxyAI is thinking...' : 'NoxyAI is processing feedback...');
|
|
110
320
|
|
|
111
321
|
try {
|
|
112
322
|
const res = await fetch(BASE_URL + '/api/io', {
|
|
113
323
|
method: 'POST',
|
|
114
|
-
headers: {
|
|
115
|
-
|
|
324
|
+
headers: {
|
|
325
|
+
'Content-Type': 'application/json',
|
|
326
|
+
'Authorization': 'Bearer ' + config.token,
|
|
327
|
+
'X-Client-Version': '1.0.0',
|
|
328
|
+
'X-Client-Platform': os.platform()
|
|
329
|
+
},
|
|
330
|
+
body: JSON.stringify({
|
|
331
|
+
prompt: enhancedPrompt,
|
|
332
|
+
model: config.model,
|
|
333
|
+
agentMode: config.agentMode,
|
|
334
|
+
context: {
|
|
335
|
+
cwd: process.cwd(),
|
|
336
|
+
platform: os.platform(),
|
|
337
|
+
shell: process.env.SHELL
|
|
338
|
+
}
|
|
339
|
+
})
|
|
116
340
|
});
|
|
117
341
|
|
|
118
342
|
stopSpinner();
|
|
119
|
-
|
|
343
|
+
|
|
344
|
+
if (!res.ok) {
|
|
345
|
+
const errorText = await res.text();
|
|
346
|
+
if (res.status === 401) {
|
|
347
|
+
console.error('\x1b[31m❌ Session expired. Please login again.\x1b[0m');
|
|
348
|
+
config.token = null;
|
|
349
|
+
saveConfig(config);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (res.status === 402) {
|
|
353
|
+
console.error('\x1b[31m❌ Insufficient credits. Visit: https://noxyai.com/pricing\x1b[0m');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
return console.error(`\x1b[31m❌ API Error (${res.status}):\x1b[0m ${errorText}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
120
359
|
console.log(`\n🤖 \x1b[36mNoxyAI (${config.model}):\x1b[0m\n`);
|
|
121
360
|
|
|
122
361
|
const reader = res.body.getReader();
|
|
123
362
|
const decoder = new TextDecoder('utf-8');
|
|
124
363
|
let fullResponse = "";
|
|
364
|
+
|
|
365
|
+
// UI State for Boxed Reasoning
|
|
366
|
+
let isFirstReasoning = true;
|
|
367
|
+
let reasoningClosed = false;
|
|
368
|
+
let actionsPerformed = [];
|
|
369
|
+
let totalCost = 0;
|
|
370
|
+
let totalTokens = 0;
|
|
125
371
|
|
|
126
372
|
while (true) {
|
|
127
373
|
const { done, value } = await reader.read();
|
|
128
374
|
if (done) break;
|
|
375
|
+
|
|
129
376
|
const lines = decoder.decode(value, { stream: true }).split('\n');
|
|
377
|
+
|
|
130
378
|
for (const line of lines) {
|
|
131
379
|
if (line.startsWith('data: ')) {
|
|
132
380
|
const data = line.slice(6).trim();
|
|
133
381
|
if (data === '[DONE]') break;
|
|
382
|
+
|
|
134
383
|
try {
|
|
135
384
|
const parsed = JSON.parse(data);
|
|
385
|
+
|
|
386
|
+
// Boxed Reasoning UI
|
|
136
387
|
if (parsed.isReasoning) {
|
|
137
|
-
|
|
388
|
+
if (isFirstReasoning) {
|
|
389
|
+
process.stdout.write('\n\x1b[35m┌── 🧠 Deep Reasoning ─────────────────────\x1b[0m\n\x1b[31m│ \x1b[0m');
|
|
390
|
+
isFirstReasoning = false;
|
|
391
|
+
}
|
|
392
|
+
const text = parsed.text.replace(/\n/g, '\n\x1b[31m│ \x1b[0m');
|
|
393
|
+
process.stdout.write('\x1b[31m' + text + '\x1b[0m');
|
|
138
394
|
} else if (parsed.text) {
|
|
395
|
+
if (!isFirstReasoning && !reasoningClosed) {
|
|
396
|
+
process.stdout.write('\n\x1b[35m└───────────────────────────────────────────\x1b[0m\n\n');
|
|
397
|
+
reasoningClosed = true;
|
|
398
|
+
}
|
|
139
399
|
fullResponse += parsed.text;
|
|
140
400
|
process.stdout.write(parsed.text);
|
|
141
401
|
} else if (parsed.cost) {
|
|
142
|
-
|
|
143
|
-
|
|
402
|
+
totalCost = parsed.cost;
|
|
403
|
+
totalTokens = parsed.tokens;
|
|
404
|
+
actionsPerformed = parsed.actions_performed || 0;
|
|
405
|
+
|
|
406
|
+
console.log(`\n\n\x1b[90m${'─'.repeat(40)}\x1b[0m`);
|
|
407
|
+
console.log(`\x1b[33m📊 Session Stats:\x1b[0m`);
|
|
408
|
+
console.log(` • Tokens: ${totalTokens}`);
|
|
409
|
+
console.log(` • Credits: ${totalCost}`);
|
|
410
|
+
if (actionsPerformed > 0) {
|
|
411
|
+
console.log(` • Actions: ${actionsPerformed}`);
|
|
412
|
+
}
|
|
413
|
+
console.log(`\x1b[90m${'─'.repeat(40)}\x1b[0m\n`);
|
|
414
|
+
} else if (parsed.actions) {
|
|
415
|
+
// Real-time action updates
|
|
416
|
+
parsed.actions.forEach(action => {
|
|
417
|
+
const icon = { search: '🔍', read: '📖', execute: '⚡', file: '📝', notify: '🔔' }[action.type] || '•';
|
|
418
|
+
console.log(`\x1b[36m${icon} Agent Action:\x1b[0m ${action.type}`);
|
|
419
|
+
});
|
|
144
420
|
}
|
|
145
|
-
} catch (e) {
|
|
421
|
+
} catch (e) {
|
|
422
|
+
// Ignore parsing errors
|
|
423
|
+
}
|
|
146
424
|
}
|
|
147
425
|
}
|
|
148
426
|
}
|
|
149
427
|
|
|
150
|
-
if (!
|
|
428
|
+
if (!isFirstReasoning && !reasoningClosed) {
|
|
429
|
+
process.stdout.write('\n\x1b[35m└───────────────────────────────────────────\x1b[0m\n\n');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!config.agentMode) {
|
|
433
|
+
console.log();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
151
436
|
|
|
152
|
-
console.log('\
|
|
437
|
+
console.log('\x1b[32m[Agent] Executing actions...\x1b[0m');
|
|
153
438
|
let agentFeedback = "";
|
|
154
439
|
|
|
155
|
-
|
|
440
|
+
// Tool: Notifications
|
|
441
|
+
const notifyRegex = /<notify>([\s\S]*?)<\/notify>/g;
|
|
442
|
+
let match;
|
|
443
|
+
while ((match = notifyRegex.exec(fullResponse)) !== null) {
|
|
444
|
+
const msg = match[1].trim();
|
|
445
|
+
console.log(`\x1b[32m🔔 Notification:\x1b[0m ${msg}`);
|
|
446
|
+
|
|
447
|
+
const platform = os.platform();
|
|
448
|
+
if (platform === 'android') {
|
|
449
|
+
spawn('termux-notification', ['-t', 'NoxyAI', '-c', msg], { stdio: 'ignore' });
|
|
450
|
+
} else if (platform === 'darwin') {
|
|
451
|
+
spawn('osascript', ['-e', `display notification "${msg.replace(/"/g, '\\"')}" with title "NoxyAI"`]);
|
|
452
|
+
} else if (platform === 'win32') {
|
|
453
|
+
spawn('powershell', ['-Command', `New-BurntToastNotification -Text "NoxyAI", "${msg}"`], { stdio: 'ignore' });
|
|
454
|
+
} else if (platform === 'linux') {
|
|
455
|
+
spawn('notify-send', ['NoxyAI', msg], { stdio: 'ignore' });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Tool: Read Files
|
|
460
|
+
const readRegex = /<read>([\s\S]*?)<\/read>/g;
|
|
156
461
|
while ((match = readRegex.exec(fullResponse)) !== null) {
|
|
157
462
|
const filePath = match[1].trim();
|
|
463
|
+
|
|
464
|
+
// Security check
|
|
465
|
+
if (filePath.includes('..') || filePath.includes('/etc/') || filePath.includes('.env')) {
|
|
466
|
+
console.log(`\x1b[31m⚠️ Security: Access denied to ${filePath}\x1b[0m`);
|
|
467
|
+
agentFeedback += `\n[SECURITY BLOCK: Cannot access ${filePath}]\n`;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
158
471
|
try {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
472
|
+
const resolvedPath = path.resolve(filePath);
|
|
473
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
474
|
+
const truncated = content.length > 5000 ? content.substring(0, 5000) + '\n... [truncated]' : content;
|
|
475
|
+
console.log(`\x1b[34m📖 Read:\x1b[0m ${filePath} (${content.length} chars)`);
|
|
476
|
+
agentFeedback += `\n[FILE: ${filePath}]\n\`\`\`\n${truncated}\n\`\`\`\n`;
|
|
477
|
+
} catch (err) {
|
|
478
|
+
console.log(`\x1b[31m❌ Read failed:\x1b[0m ${filePath} - ${err.message}`);
|
|
479
|
+
agentFeedback += `\n[ERROR reading ${filePath}: ${err.message}]\n`;
|
|
480
|
+
}
|
|
163
481
|
}
|
|
164
482
|
|
|
483
|
+
// Tool: Web Search (Enhanced with Google)
|
|
165
484
|
const searchRegex = /<search>([\s\S]*?)<\/search>/g;
|
|
166
485
|
while ((match = searchRegex.exec(fullResponse)) !== null) {
|
|
167
486
|
const query = match[1].trim();
|
|
168
|
-
console.log(`\x1b[35m🔍 Searching
|
|
487
|
+
console.log(`\x1b[35m🔍 Searching:\x1b[0m ${query}`);
|
|
488
|
+
|
|
489
|
+
// Check cache first
|
|
490
|
+
const cached = getCachedSearch(query);
|
|
491
|
+
if (cached) {
|
|
492
|
+
console.log(`\x1b[90m ↳ Using cached results (${cached.length} items)\x1b[0m`);
|
|
493
|
+
let snippets = cached.map((r, i) =>
|
|
494
|
+
`${i + 1}. **${r.title}**\n ${r.snippet}\n 🔗 ${r.link}`
|
|
495
|
+
).join('\n\n');
|
|
496
|
+
agentFeedback += `\n[SEARCH RESULTS (cached): "${query}"]\n${snippets}\n`;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
169
500
|
try {
|
|
170
501
|
const searchRes = await fetch(BASE_URL + '/api/search', {
|
|
171
502
|
method: 'POST',
|
|
172
|
-
headers: {
|
|
173
|
-
|
|
503
|
+
headers: {
|
|
504
|
+
'Content-Type': 'application/json',
|
|
505
|
+
'Authorization': 'Bearer ' + config.token
|
|
506
|
+
},
|
|
507
|
+
body: JSON.stringify({ query, num: 5 })
|
|
174
508
|
});
|
|
175
509
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
searchData = JSON.parse(responseText);
|
|
181
|
-
} catch (parseError) {
|
|
182
|
-
throw new Error("Search backend returned an invalid response (Likely a 500 error or missing Vercel environment variables).");
|
|
510
|
+
const searchData = await searchRes.json();
|
|
511
|
+
|
|
512
|
+
if (!searchRes.ok) {
|
|
513
|
+
throw new Error(searchData.error || `HTTP ${searchRes.status}`);
|
|
183
514
|
}
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.log(`
|
|
192
|
-
|
|
193
|
-
|
|
515
|
+
|
|
516
|
+
if (searchData.results && searchData.results.length > 0) {
|
|
517
|
+
cacheSearchResults(query, searchData.results);
|
|
518
|
+
|
|
519
|
+
console.log(`\x1b[32m📚 Found ${searchData.results.length} results:\x1b[0m`);
|
|
520
|
+
let snippets = "";
|
|
521
|
+
searchData.results.forEach((item, i) => {
|
|
522
|
+
console.log(` \x1b[36m${i + 1}.\x1b[0m ${item.title}`);
|
|
523
|
+
console.log(` \x1b[90m${item.link}\x1b[0m`);
|
|
524
|
+
snippets += `${i + 1}. **${item.title}**\n ${item.snippet}\n 🔗 ${item.link}\n\n`;
|
|
525
|
+
});
|
|
526
|
+
agentFeedback += `\n[SEARCH RESULTS: "${query}"]\n${snippets}\n`;
|
|
527
|
+
} else {
|
|
528
|
+
console.log(`\x1b[33m⚠️ No results found\x1b[0m`);
|
|
529
|
+
agentFeedback += `\n[SEARCH: No results for "${query}"]\n`;
|
|
194
530
|
}
|
|
195
|
-
agentFeedback += `\n[SEARCH RESULTS FOR "${query}"]\n${snippets}\n`;
|
|
196
531
|
} catch (err) {
|
|
197
|
-
console.log(`\x1b[31m❌ Search
|
|
198
|
-
agentFeedback += `\n[SEARCH
|
|
532
|
+
console.log(`\x1b[31m❌ Search failed:\x1b[0m ${err.message}`);
|
|
533
|
+
agentFeedback += `\n[SEARCH ERROR: ${err.message}]\n`;
|
|
199
534
|
}
|
|
200
535
|
}
|
|
201
536
|
|
|
537
|
+
// Tool: Write Files
|
|
202
538
|
const fileRegex = /<file path="([^"]+)">([\s\S]*?)<\/file>/g;
|
|
203
539
|
while ((match = fileRegex.exec(fullResponse)) !== null) {
|
|
204
540
|
const filePath = match[1], content = match[2];
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
541
|
+
|
|
542
|
+
// Security checks
|
|
543
|
+
if (filePath.includes('..') || filePath.includes('/etc/') ||
|
|
544
|
+
filePath.includes('.env') || filePath.includes('.ssh/')) {
|
|
545
|
+
console.log(`\x1b[31m⚠️ Security: Write blocked for ${filePath}\x1b[0m`);
|
|
546
|
+
agentFeedback += `\n[SECURITY BLOCK: Cannot write to ${filePath}]\n`;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
const resolvedPath = path.resolve(filePath);
|
|
552
|
+
const dir = path.dirname(resolvedPath);
|
|
553
|
+
|
|
554
|
+
if (!fs.existsSync(dir)) {
|
|
555
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Backup existing file
|
|
559
|
+
if (fs.existsSync(resolvedPath)) {
|
|
560
|
+
const backup = resolvedPath + '.bak';
|
|
561
|
+
fs.copyFileSync(resolvedPath, backup);
|
|
562
|
+
console.log(`\x1b[90m ↳ Backup created: ${path.basename(backup)}\x1b[0m`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
fs.writeFileSync(resolvedPath, content.trim());
|
|
566
|
+
console.log(`\x1b[32m✔ Created:\x1b[0m ${filePath} (${content.length} chars)`);
|
|
567
|
+
agentFeedback += `\n[FILE CREATED: ${filePath}]\n`;
|
|
568
|
+
} catch (err) {
|
|
569
|
+
console.log(`\x1b[31m❌ Write failed:\x1b[0m ${filePath} - ${err.message}`);
|
|
570
|
+
agentFeedback += `\n[ERROR writing ${filePath}: ${err.message}]\n`;
|
|
571
|
+
}
|
|
209
572
|
}
|
|
210
573
|
|
|
574
|
+
// Tool: Execute Commands (Safe Mode)
|
|
211
575
|
const execRegex = /<execute>([\s\S]*?)<\/execute>/g;
|
|
212
576
|
const commands = [];
|
|
213
|
-
while ((match = execRegex.exec(fullResponse)) !== null)
|
|
577
|
+
while ((match = execRegex.exec(fullResponse)) !== null) {
|
|
578
|
+
commands.push(match[1].trim());
|
|
579
|
+
}
|
|
580
|
+
|
|
214
581
|
for (const cmd of commands) {
|
|
215
|
-
|
|
216
|
-
|
|
582
|
+
// Security: Block dangerous commands
|
|
583
|
+
const dangerous = ['rm -rf /', 'sudo rm', 'dd if=', 'mkfs', ':(){', 'chmod 777 /', '> /dev/sda'];
|
|
584
|
+
if (dangerous.some(d => cmd.toLowerCase().includes(d))) {
|
|
585
|
+
console.log(`\x1b[31m🛑 BLOCKED:\x1b[0m Dangerous command prevented`);
|
|
586
|
+
agentFeedback += `\n[SECURITY BLOCK: Command rejected - ${cmd}]\n`;
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (config.safeMode && !config.autoApprove) {
|
|
591
|
+
const answer = await askConfirm(
|
|
592
|
+
`\n\x1b[33m⚠️ Execute:\x1b[0m \x1b[36m${cmd}\x1b[0m\n` +
|
|
593
|
+
`\x1b[90m[Y]es / [N]o / [A]lways for session / [B]ackground:\x1b[0m `
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
const lower = answer.toLowerCase();
|
|
597
|
+
if (lower === 'n') {
|
|
598
|
+
console.log('\x1b[31m✗ Skipped\x1b[0m');
|
|
599
|
+
agentFeedback += `\n[USER SKIPPED: ${cmd}]\n`;
|
|
600
|
+
continue;
|
|
601
|
+
} else if (lower === 'a') {
|
|
602
|
+
config.autoApprove = true;
|
|
603
|
+
saveConfig(config);
|
|
604
|
+
console.log('\x1b[32m✓ Auto-approve enabled for this session\x1b[0m');
|
|
605
|
+
} else if (lower === 'b') {
|
|
606
|
+
console.log('\x1b[36m⚡ Running in background...\x1b[0m');
|
|
607
|
+
try {
|
|
608
|
+
const result = await runTerminalCommand(cmd, { background: true });
|
|
609
|
+
console.log(`\x1b[32m✓ Started (PID: ${result.pid})\x1b[0m`);
|
|
610
|
+
agentFeedback += `\n[COMMAND STARTED (background): ${cmd}]\n`;
|
|
611
|
+
} catch (err) {
|
|
612
|
+
console.log(`\x1b[31m❌ Failed:\x1b[0m ${err.message}`);
|
|
613
|
+
agentFeedback += `\n[ERROR: ${err.message}]\n`;
|
|
614
|
+
}
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
try {
|
|
620
|
+
await runTerminalCommand(cmd);
|
|
621
|
+
agentFeedback += `\n[COMMAND SUCCEEDED: ${cmd}]\n`;
|
|
622
|
+
} catch (err) {
|
|
623
|
+
console.log(`\x1b[31m❌ Failed:\x1b[0m ${err.message}`);
|
|
624
|
+
agentFeedback += `\n[COMMAND ERROR: ${cmd} - ${err.message}]\n`;
|
|
625
|
+
}
|
|
217
626
|
}
|
|
218
627
|
|
|
628
|
+
// Reset auto-approve after session
|
|
629
|
+
if (config.autoApprove) {
|
|
630
|
+
config.autoApprove = false;
|
|
631
|
+
saveConfig(config);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Recursive feedback loop
|
|
219
635
|
if (agentFeedback) {
|
|
220
|
-
console.log(`\x1b[33m🔄
|
|
221
|
-
await chat(
|
|
636
|
+
console.log(`\n\x1b[33m🔄 Processing results with Agent...\x1b[0m`);
|
|
637
|
+
await chat(
|
|
638
|
+
`Action results:\n${agentFeedback}\n\n` +
|
|
639
|
+
`Based on these results, what should be the next step? ` +
|
|
640
|
+
`Respond with appropriate XML tags (<read>, <search>, <file>, <execute>, <notify>) ` +
|
|
641
|
+
`or "TASK_COMPLETE" if finished.`,
|
|
642
|
+
depth + 1
|
|
643
|
+
);
|
|
222
644
|
return;
|
|
223
645
|
}
|
|
224
646
|
|
|
225
|
-
console.log('\n\x1b[32m
|
|
226
|
-
console.log(`\x1b[32m✨ Task
|
|
227
|
-
console.log('\x1b[32m
|
|
647
|
+
console.log('\n\x1b[32m' + '═'.repeat(40) + '\x1b[0m');
|
|
648
|
+
console.log(`\x1b[32m✨ Task completed successfully!\x1b[0m`);
|
|
649
|
+
console.log('\x1b[32m' + '═'.repeat(40) + '\x1b[0m\n');
|
|
228
650
|
|
|
229
|
-
} catch (error) {
|
|
651
|
+
} catch (error) {
|
|
652
|
+
stopSpinner();
|
|
653
|
+
console.error('\n\x1b[31m❌ Connection error:\x1b[0m ' + error.message);
|
|
654
|
+
}
|
|
230
655
|
}
|
|
231
656
|
|
|
232
657
|
function startInteractiveMode() {
|
|
233
658
|
const config = loadConfig();
|
|
234
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
235
659
|
printLogo();
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
660
|
+
|
|
661
|
+
if (!config.token) {
|
|
662
|
+
console.log('\x1b[33mWelcome to NoxyAI!\x1b[0m');
|
|
663
|
+
console.log('\x1b[36mTo get started, run:\x1b[0m \x1b[32mnoxyai login\x1b[0m\n');
|
|
664
|
+
process.exit(0);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
console.log(`\x1b[33mWelcome back${config.userEmail ? ', ' + config.userEmail : ''}!\x1b[0m`);
|
|
668
|
+
console.log(`\n\x1b[90m${'─'.repeat(40)}\x1b[0m`);
|
|
669
|
+
console.log(`Model: \x1b[32m${config.model}\x1b[0m`);
|
|
670
|
+
console.log(`Agent Mode: ${config.agentMode ? '\x1b[32m● ON\x1b[0m' : '\x1b[31m○ OFF\x1b[0m'}`);
|
|
671
|
+
console.log(`Safe Mode: ${config.safeMode ? '\x1b[32m● ON\x1b[0m' : '\x1b[33m○ OFF\x1b[0m'}`);
|
|
672
|
+
console.log(`\x1b[90m${'─'.repeat(40)}\x1b[0m`);
|
|
673
|
+
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`);
|
|
674
|
+
console.log(`Type your question or task below.\n`);
|
|
239
675
|
|
|
240
676
|
function ask() {
|
|
241
|
-
rl.question('\x1b[
|
|
677
|
+
rl.question('\x1b[36m⟫\x1b[0m ', async (input) => {
|
|
242
678
|
const trimmed = input.trim();
|
|
243
679
|
const lower = trimmed.toLowerCase();
|
|
244
680
|
|
|
245
|
-
if (lower === 'exit' || lower === '/exit')
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
console.
|
|
681
|
+
if (lower === 'exit' || lower === '/exit' || lower === '/quit') {
|
|
682
|
+
console.log('\n\x1b[36mGoodbye! 👋\x1b[0m\n');
|
|
683
|
+
process.exit(0);
|
|
684
|
+
} else if (lower === '/clear') {
|
|
685
|
+
console.clear();
|
|
686
|
+
printLogo();
|
|
687
|
+
ask();
|
|
688
|
+
} else if (lower === '/agent') {
|
|
689
|
+
const c = loadConfig();
|
|
690
|
+
c.agentMode = !c.agentMode;
|
|
691
|
+
saveConfig(c);
|
|
692
|
+
console.log(`\n\x1b[33mAgent Mode: ${c.agentMode ? '\x1b[32m● ON (File/Web/Execute access)' : '\x1b[31m○ OFF (Chat only)'}\x1b[0m\n`);
|
|
250
693
|
ask();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
console.log('
|
|
256
|
-
|
|
257
|
-
|
|
694
|
+
} else if (lower === '/safe') {
|
|
695
|
+
const c = loadConfig();
|
|
696
|
+
c.safeMode = !c.safeMode;
|
|
697
|
+
saveConfig(c);
|
|
698
|
+
console.log(`\n\x1b[33mSafe Mode: ${c.safeMode ? '\x1b[32m● ON (Command confirmation required)' : '\x1b[33m○ OFF (Auto-execute enabled - use with caution!)'}\x1b[0m\n`);
|
|
699
|
+
ask();
|
|
700
|
+
} else if (lower === '/status') {
|
|
701
|
+
const c = loadConfig();
|
|
702
|
+
console.log(`\n\x1b[90m${'─'.repeat(40)}\x1b[0m`);
|
|
703
|
+
console.log(`\x1b[36mSession Status:\x1b[0m`);
|
|
704
|
+
console.log(` • User: ${c.userEmail || 'Unknown'}`);
|
|
705
|
+
console.log(` • Model: ${c.model}`);
|
|
706
|
+
console.log(` • Agent Mode: ${c.agentMode ? 'ON' : 'OFF'}`);
|
|
707
|
+
console.log(` • Safe Mode: ${c.safeMode ? 'ON' : 'OFF'}`);
|
|
708
|
+
console.log(` • CWD: ${process.cwd()}`);
|
|
709
|
+
console.log(`\x1b[90m${'─'.repeat(40)}\x1b[0m\n`);
|
|
710
|
+
ask();
|
|
711
|
+
} else if (lower === '/model') {
|
|
712
|
+
console.log('\n\x1b[33mSelect AI Model:\x1b[0m');
|
|
713
|
+
console.log(' \x1b[36m1)\x1b[0m Auto (Llama 3.3 70B) - Fast & Capable');
|
|
714
|
+
console.log(' \x1b[36m2)\x1b[0m Qwen3 Next 80B - Deep Reasoning');
|
|
715
|
+
console.log(' \x1b[36m3)\x1b[0m Mistral Mamba Codestral 7B - Code Optimized');
|
|
716
|
+
|
|
717
|
+
rl.question('\n\x1b[36mChoice (1-3):\x1b[0m ', (choice) => {
|
|
258
718
|
let selected = 'auto';
|
|
259
|
-
|
|
260
|
-
if (choice === '
|
|
261
|
-
|
|
262
|
-
|
|
719
|
+
let modelName = 'Auto (Llama 3.3 70B)';
|
|
720
|
+
if (choice === '2') { selected = 'Qwen3'; modelName = 'Qwen3 Next 80B'; }
|
|
721
|
+
if (choice === '3') { selected = 'Mamba'; modelName = 'Mistral Mamba Codestral 7B'; }
|
|
722
|
+
|
|
723
|
+
const c = loadConfig();
|
|
724
|
+
c.model = selected;
|
|
725
|
+
saveConfig(c);
|
|
726
|
+
console.log(`\n\x1b[32m✓ Model changed to: ${modelName}\x1b[0m\n`);
|
|
727
|
+
ask();
|
|
263
728
|
});
|
|
264
729
|
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
730
|
+
} else if (trimmed) {
|
|
731
|
+
await chat(trimmed);
|
|
732
|
+
ask();
|
|
733
|
+
} else {
|
|
734
|
+
ask();
|
|
735
|
+
}
|
|
268
736
|
});
|
|
269
737
|
}
|
|
738
|
+
|
|
270
739
|
ask();
|
|
271
740
|
}
|
|
272
741
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
else if (
|
|
277
|
-
|
|
742
|
+
// CLI Command Router
|
|
743
|
+
if (command === 'login') {
|
|
744
|
+
login();
|
|
745
|
+
} else if (command === 'logout') {
|
|
746
|
+
const c = loadConfig();
|
|
747
|
+
c.token = null;
|
|
748
|
+
c.userEmail = null;
|
|
749
|
+
saveConfig(c);
|
|
750
|
+
console.log('\x1b[32m✓ Successfully logged out.\x1b[0m\n');
|
|
751
|
+
process.exit(0);
|
|
752
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
753
|
+
printLogo();
|
|
754
|
+
console.log(`
|
|
755
|
+
\x1b[33mNoxyAI - Your Autonomous Terminal Agent\x1b[0m
|
|
756
|
+
|
|
757
|
+
\x1b[36mUsage:\x1b[0m
|
|
758
|
+
noxyai Start interactive mode
|
|
759
|
+
noxyai login Authenticate with your NoxyAI account
|
|
760
|
+
noxyai logout Remove local authentication
|
|
761
|
+
noxyai help Show this help message
|
|
762
|
+
|
|
763
|
+
\x1b[36mInteractive Commands:\x1b[0m
|
|
764
|
+
/model Switch AI model
|
|
765
|
+
/agent Toggle agent mode (file/web/execute access)
|
|
766
|
+
/safe Toggle safe mode (command confirmation)
|
|
767
|
+
/status Show current session status
|
|
768
|
+
/clear Clear the screen
|
|
769
|
+
/exit Exit the program
|
|
770
|
+
|
|
771
|
+
\x1b[36mExamples:\x1b[0m
|
|
772
|
+
$ noxyai login
|
|
773
|
+
$ noxyai
|
|
774
|
+
> Create a React component for a login form
|
|
775
|
+
> Search for Python asyncio best practices
|
|
776
|
+
> Optimize this Dockerfile
|
|
777
|
+
|
|
778
|
+
\x1b[90mDocumentation: https://noxyai.com/docs\x1b[0m
|
|
779
|
+
`);
|
|
780
|
+
process.exit(0);
|
|
781
|
+
} else if (command === 'version' || command === '--version' || command === '-v') {
|
|
782
|
+
console.log('NoxyAI CLI v1.0.0');
|
|
783
|
+
process.exit(0);
|
|
784
|
+
} else if (!command) {
|
|
785
|
+
startInteractiveMode();
|
|
786
|
+
} else if (command.startsWith('-')) {
|
|
787
|
+
console.error(`\x1b[31m❌ Unknown option: ${command}\x1b[0m`);
|
|
788
|
+
console.error(`\x1b[90mRun 'noxyai help' for usage information.\x1b[0m\n`);
|
|
789
|
+
process.exit(1);
|
|
790
|
+
} else {
|
|
791
|
+
// Direct query mode: noxyai "your question here"
|
|
792
|
+
const query = args.join(' ');
|
|
793
|
+
const config = loadConfig();
|
|
794
|
+
|
|
795
|
+
if (!config.token) {
|
|
796
|
+
console.error('\x1b[31m❌ Not logged in. Run "noxyai login" first.\x1b[0m\n');
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
chat(query).then(() => process.exit(0));
|
|
801
|
+
}
|