npm-noxyai 1.0.17 → 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 +613 -122
- package/package.json +1 -1
package/index-noxyai.js
CHANGED
|
@@ -3,23 +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');
|
|
11
14
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
12
15
|
|
|
16
|
+
// Ensure cache directory exists
|
|
17
|
+
if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
18
|
+
|
|
13
19
|
const args = process.argv.slice(2);
|
|
14
20
|
const command = args[0];
|
|
15
21
|
|
|
22
|
+
// Rate limiting store (in-memory, resets on CLI restart)
|
|
23
|
+
const rateLimitStore = new Map();
|
|
24
|
+
|
|
16
25
|
function loadConfig() {
|
|
17
26
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
18
|
-
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;
|
|
19
80
|
}
|
|
20
|
-
return { token: null, model: 'auto', agentMode: false };
|
|
21
81
|
}
|
|
22
|
-
function saveConfig(config) { fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); }
|
|
23
82
|
|
|
24
83
|
function printLogo() {
|
|
25
84
|
console.log('\x1b[36m' + `
|
|
@@ -47,77 +106,256 @@ function stopSpinner() {
|
|
|
47
106
|
process.stdout.write('\r\x1b[K\x1b[?25h');
|
|
48
107
|
}
|
|
49
108
|
|
|
109
|
+
// Enhanced login with device flow
|
|
50
110
|
async function login() {
|
|
51
111
|
printLogo();
|
|
52
|
-
console.log('Initializing secure login...\n');
|
|
112
|
+
console.log('\x1b[33m🔐 Initializing secure device login...\x1b[0m\n');
|
|
53
113
|
try {
|
|
54
|
-
const initRes = await fetch(BASE_URL + '/api/cli/init', {
|
|
55
|
-
|
|
56
|
-
|
|
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`);
|
|
126
|
+
|
|
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) {}
|
|
57
136
|
|
|
58
|
-
rl.question('
|
|
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' });
|
|
148
|
+
|
|
62
149
|
startSpinner('Waiting for authentication...');
|
|
63
150
|
});
|
|
64
151
|
|
|
152
|
+
const startTime = Date.now();
|
|
153
|
+
const maxTime = expiresIn * 1000;
|
|
65
154
|
const pollInterval = setInterval(async () => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.clear(); printLogo();
|
|
72
|
-
console.log('✅ Login successful! Type "noxyai" to start.\n'); process.exit(0);
|
|
73
|
-
} else if (data.error) {
|
|
74
|
-
clearInterval(pollInterval); stopSpinner();
|
|
75
|
-
console.error('\n❌ Login failed: ' + data.error); process.exit(1);
|
|
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);
|
|
76
160
|
}
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
194
|
+
}
|
|
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
|
+
}
|
|
79
201
|
}
|
|
80
202
|
|
|
81
203
|
const askConfirm = (query) => new Promise(resolve => rl.question(query, resolve));
|
|
82
204
|
|
|
83
|
-
function runTerminalCommand(cmd) {
|
|
205
|
+
function runTerminalCommand(cmd, options = {}) {
|
|
84
206
|
return new Promise((resolve, reject) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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) {
|
|
89
230
|
console.log(`\x1b[36m[i] Server detected. Running in foreground. Press Ctrl+C to stop.\x1b[0m`);
|
|
90
|
-
setTimeout(() => resolve(), 2500);
|
|
231
|
+
setTimeout(() => resolve({ server: true }), 2500);
|
|
91
232
|
}
|
|
92
|
-
|
|
93
|
-
|
|
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());
|
|
238
|
+
}
|
|
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));
|
|
94
248
|
});
|
|
95
249
|
}
|
|
96
250
|
|
|
251
|
+
// Enhanced context gathering
|
|
97
252
|
function getLocalContext() {
|
|
98
253
|
try {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
}));
|
|
102
301
|
}
|
|
103
302
|
|
|
104
303
|
async function chat(prompt, depth = 0) {
|
|
105
304
|
const config = loadConfig();
|
|
106
|
-
if (!config.token) {
|
|
107
|
-
|
|
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
|
+
}
|
|
108
314
|
|
|
109
|
-
const enhancedPrompt = (depth === 0 && config.agentMode)
|
|
110
|
-
|
|
315
|
+
const enhancedPrompt = (depth === 0 && config.agentMode)
|
|
316
|
+
? getLocalContext() + prompt
|
|
317
|
+
: prompt;
|
|
318
|
+
|
|
319
|
+
startSpinner(depth === 0 ? 'NoxyAI is thinking...' : 'NoxyAI is processing feedback...');
|
|
111
320
|
|
|
112
321
|
try {
|
|
113
322
|
const res = await fetch(BASE_URL + '/api/io', {
|
|
114
323
|
method: 'POST',
|
|
115
|
-
headers: {
|
|
116
|
-
|
|
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
|
+
})
|
|
117
340
|
});
|
|
118
341
|
|
|
119
342
|
stopSpinner();
|
|
120
|
-
|
|
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
|
+
|
|
121
359
|
console.log(`\n🤖 \x1b[36mNoxyAI (${config.model}):\x1b[0m\n`);
|
|
122
360
|
|
|
123
361
|
const reader = res.body.getReader();
|
|
@@ -127,22 +365,28 @@ async function chat(prompt, depth = 0) {
|
|
|
127
365
|
// UI State for Boxed Reasoning
|
|
128
366
|
let isFirstReasoning = true;
|
|
129
367
|
let reasoningClosed = false;
|
|
368
|
+
let actionsPerformed = [];
|
|
369
|
+
let totalCost = 0;
|
|
370
|
+
let totalTokens = 0;
|
|
130
371
|
|
|
131
372
|
while (true) {
|
|
132
373
|
const { done, value } = await reader.read();
|
|
133
374
|
if (done) break;
|
|
375
|
+
|
|
134
376
|
const lines = decoder.decode(value, { stream: true }).split('\n');
|
|
377
|
+
|
|
135
378
|
for (const line of lines) {
|
|
136
379
|
if (line.startsWith('data: ')) {
|
|
137
380
|
const data = line.slice(6).trim();
|
|
138
381
|
if (data === '[DONE]') break;
|
|
382
|
+
|
|
139
383
|
try {
|
|
140
384
|
const parsed = JSON.parse(data);
|
|
141
385
|
|
|
142
|
-
//
|
|
386
|
+
// Boxed Reasoning UI
|
|
143
387
|
if (parsed.isReasoning) {
|
|
144
388
|
if (isFirstReasoning) {
|
|
145
|
-
process.stdout.write('\n\x1b[35m┌── 🧠
|
|
389
|
+
process.stdout.write('\n\x1b[35m┌── 🧠 Deep Reasoning ─────────────────────\x1b[0m\n\x1b[31m│ \x1b[0m');
|
|
146
390
|
isFirstReasoning = false;
|
|
147
391
|
}
|
|
148
392
|
const text = parsed.text.replace(/\n/g, '\n\x1b[31m│ \x1b[0m');
|
|
@@ -155,9 +399,28 @@ async function chat(prompt, depth = 0) {
|
|
|
155
399
|
fullResponse += parsed.text;
|
|
156
400
|
process.stdout.write(parsed.text);
|
|
157
401
|
} else if (parsed.cost) {
|
|
158
|
-
|
|
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
|
+
});
|
|
159
420
|
}
|
|
160
|
-
} catch (e) {
|
|
421
|
+
} catch (e) {
|
|
422
|
+
// Ignore parsing errors
|
|
423
|
+
}
|
|
161
424
|
}
|
|
162
425
|
}
|
|
163
426
|
}
|
|
@@ -166,145 +429,373 @@ async function chat(prompt, depth = 0) {
|
|
|
166
429
|
process.stdout.write('\n\x1b[35m└───────────────────────────────────────────\x1b[0m\n\n');
|
|
167
430
|
}
|
|
168
431
|
|
|
169
|
-
if (!config.agentMode) {
|
|
432
|
+
if (!config.agentMode) {
|
|
433
|
+
console.log();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
170
436
|
|
|
171
|
-
console.log('\
|
|
437
|
+
console.log('\x1b[32m[Agent] Executing actions...\x1b[0m');
|
|
172
438
|
let agentFeedback = "";
|
|
173
439
|
|
|
174
|
-
// Tool:
|
|
175
|
-
const notifyRegex = /<notify>([\s\S]*?)<\/notify>/g;
|
|
440
|
+
// Tool: Notifications
|
|
441
|
+
const notifyRegex = /<notify>([\s\S]*?)<\/notify>/g;
|
|
442
|
+
let match;
|
|
176
443
|
while ((match = notifyRegex.exec(fullResponse)) !== null) {
|
|
177
444
|
const msg = match[1].trim();
|
|
178
|
-
console.log(`\x1b[32m🔔 Notification
|
|
445
|
+
console.log(`\x1b[32m🔔 Notification:\x1b[0m ${msg}`);
|
|
446
|
+
|
|
179
447
|
const platform = os.platform();
|
|
180
|
-
if (platform === 'android')
|
|
181
|
-
|
|
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
|
+
}
|
|
182
457
|
}
|
|
183
458
|
|
|
184
|
-
// Tool: Read
|
|
459
|
+
// Tool: Read Files
|
|
185
460
|
const readRegex = /<read>([\s\S]*?)<\/read>/g;
|
|
186
461
|
while ((match = readRegex.exec(fullResponse)) !== null) {
|
|
187
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
|
+
|
|
188
471
|
try {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
}
|
|
193
481
|
}
|
|
194
482
|
|
|
195
|
-
// Tool: Search
|
|
483
|
+
// Tool: Web Search (Enhanced with Google)
|
|
196
484
|
const searchRegex = /<search>([\s\S]*?)<\/search>/g;
|
|
197
485
|
while ((match = searchRegex.exec(fullResponse)) !== null) {
|
|
198
486
|
const query = match[1].trim();
|
|
199
|
-
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
|
+
|
|
200
500
|
try {
|
|
201
501
|
const searchRes = await fetch(BASE_URL + '/api/search', {
|
|
202
502
|
method: 'POST',
|
|
203
|
-
headers: {
|
|
204
|
-
|
|
503
|
+
headers: {
|
|
504
|
+
'Content-Type': 'application/json',
|
|
505
|
+
'Authorization': 'Bearer ' + config.token
|
|
506
|
+
},
|
|
507
|
+
body: JSON.stringify({ query, num: 5 })
|
|
205
508
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (searchData.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
509
|
+
|
|
510
|
+
const searchData = await searchRes.json();
|
|
511
|
+
|
|
512
|
+
if (!searchRes.ok) {
|
|
513
|
+
throw new Error(searchData.error || `HTTP ${searchRes.status}`);
|
|
514
|
+
}
|
|
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`;
|
|
219
530
|
}
|
|
220
|
-
agentFeedback += `\n[SEARCH RESULTS FOR "${query}"]\n${snippets}\n`;
|
|
221
531
|
} catch (err) {
|
|
222
|
-
console.log(`\x1b[31m❌ Search
|
|
223
|
-
agentFeedback += `\n[SEARCH
|
|
532
|
+
console.log(`\x1b[31m❌ Search failed:\x1b[0m ${err.message}`);
|
|
533
|
+
agentFeedback += `\n[SEARCH ERROR: ${err.message}]\n`;
|
|
224
534
|
}
|
|
225
535
|
}
|
|
226
536
|
|
|
227
|
-
// Tool: Write
|
|
537
|
+
// Tool: Write Files
|
|
228
538
|
const fileRegex = /<file path="([^"]+)">([\s\S]*?)<\/file>/g;
|
|
229
539
|
while ((match = fileRegex.exec(fullResponse)) !== null) {
|
|
230
540
|
const filePath = match[1], content = match[2];
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
+
}
|
|
235
572
|
}
|
|
236
573
|
|
|
237
|
-
//
|
|
574
|
+
// Tool: Execute Commands (Safe Mode)
|
|
238
575
|
const execRegex = /<execute>([\s\S]*?)<\/execute>/g;
|
|
239
576
|
const commands = [];
|
|
240
|
-
while ((match = execRegex.exec(fullResponse)) !== null)
|
|
577
|
+
while ((match = execRegex.exec(fullResponse)) !== null) {
|
|
578
|
+
commands.push(match[1].trim());
|
|
579
|
+
}
|
|
241
580
|
|
|
242
581
|
for (const cmd of commands) {
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
|
|
246
|
-
|
|
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`;
|
|
247
587
|
continue;
|
|
248
588
|
}
|
|
249
|
-
|
|
250
|
-
|
|
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
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Reset auto-approve after session
|
|
629
|
+
if (config.autoApprove) {
|
|
630
|
+
config.autoApprove = false;
|
|
631
|
+
saveConfig(config);
|
|
251
632
|
}
|
|
252
633
|
|
|
634
|
+
// Recursive feedback loop
|
|
253
635
|
if (agentFeedback) {
|
|
254
|
-
console.log(`\x1b[33m🔄
|
|
255
|
-
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
|
+
);
|
|
256
644
|
return;
|
|
257
645
|
}
|
|
258
646
|
|
|
259
|
-
console.log('\n\x1b[32m
|
|
260
|
-
console.log(`\x1b[32m✨ Task
|
|
261
|
-
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');
|
|
262
650
|
|
|
263
|
-
} catch (error) {
|
|
651
|
+
} catch (error) {
|
|
652
|
+
stopSpinner();
|
|
653
|
+
console.error('\n\x1b[31m❌ Connection error:\x1b[0m ' + error.message);
|
|
654
|
+
}
|
|
264
655
|
}
|
|
265
656
|
|
|
266
657
|
function startInteractiveMode() {
|
|
267
658
|
const config = loadConfig();
|
|
268
659
|
printLogo();
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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`);
|
|
272
675
|
|
|
273
676
|
function ask() {
|
|
274
|
-
rl.question('\x1b[
|
|
677
|
+
rl.question('\x1b[36m⟫\x1b[0m ', async (input) => {
|
|
275
678
|
const trimmed = input.trim();
|
|
276
679
|
const lower = trimmed.toLowerCase();
|
|
277
680
|
|
|
278
|
-
if (lower === 'exit' || lower === '/exit')
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
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`);
|
|
283
693
|
ask();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
console.log('
|
|
289
|
-
|
|
290
|
-
|
|
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) => {
|
|
291
718
|
let selected = 'auto';
|
|
292
|
-
|
|
293
|
-
if (choice === '
|
|
294
|
-
|
|
295
|
-
|
|
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();
|
|
296
728
|
});
|
|
297
729
|
return;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
730
|
+
} else if (trimmed) {
|
|
731
|
+
await chat(trimmed);
|
|
732
|
+
ask();
|
|
733
|
+
} else {
|
|
734
|
+
ask();
|
|
735
|
+
}
|
|
301
736
|
});
|
|
302
737
|
}
|
|
738
|
+
|
|
303
739
|
ask();
|
|
304
740
|
}
|
|
305
741
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
else if (
|
|
310
|
-
|
|
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
|
+
}
|