cli-browser 1.0.0
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/README.md +309 -0
- package/bin/cli-browser.js +8 -0
- package/package.json +50 -0
- package/src/bangs.js +53 -0
- package/src/bookmarks.js +45 -0
- package/src/browser.js +1277 -0
- package/src/config.js +51 -0
- package/src/developer.js +246 -0
- package/src/download.js +80 -0
- package/src/history.js +58 -0
- package/src/index.js +45 -0
- package/src/network.js +56 -0
- package/src/plugins.js +74 -0
- package/src/renderer.js +259 -0
- package/src/search.js +113 -0
- package/src/sessions.js +37 -0
- package/src/stats.js +66 -0
- package/src/tabs.js +147 -0
- package/src/themes.js +137 -0
- package/src/ui.js +202 -0
package/src/browser.js
ADDED
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import config from './config.js';
|
|
3
|
+
import search from './search.js';
|
|
4
|
+
import renderer from './renderer.js';
|
|
5
|
+
import tabs from './tabs.js';
|
|
6
|
+
import history from './history.js';
|
|
7
|
+
import bookmarks from './bookmarks.js';
|
|
8
|
+
import developer from './developer.js';
|
|
9
|
+
import downloads from './download.js';
|
|
10
|
+
import network from './network.js';
|
|
11
|
+
import stats from './stats.js';
|
|
12
|
+
import bangs from './bangs.js';
|
|
13
|
+
import plugins from './plugins.js';
|
|
14
|
+
import sessions from './sessions.js';
|
|
15
|
+
import { theme, THEMES } from './themes.js';
|
|
16
|
+
import * as ui from './ui.js';
|
|
17
|
+
import ora from 'ora';
|
|
18
|
+
|
|
19
|
+
class Browser {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.lastSearchResults = [];
|
|
22
|
+
this.currentPage = null;
|
|
23
|
+
this.running = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async fetchPage(url) {
|
|
27
|
+
const spinner = ora({
|
|
28
|
+
text: theme.muted('Loading...'),
|
|
29
|
+
spinner: 'dots',
|
|
30
|
+
color: 'cyan',
|
|
31
|
+
}).start();
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const res = await axios.get(url, {
|
|
35
|
+
headers: { 'User-Agent': config.get('userAgent') },
|
|
36
|
+
timeout: 15000,
|
|
37
|
+
maxRedirects: 5,
|
|
38
|
+
validateStatus: (status) => status < 500,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
spinner.stop();
|
|
42
|
+
stats.incrementPageVisit();
|
|
43
|
+
history.add({ type: 'page', title: '', url });
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
html: res.data,
|
|
47
|
+
headers: res.headers,
|
|
48
|
+
status: res.status,
|
|
49
|
+
url: res.request?.res?.responseUrl || url,
|
|
50
|
+
};
|
|
51
|
+
} catch (err) {
|
|
52
|
+
spinner.stop();
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async handleCommand(input) {
|
|
58
|
+
const trimmed = input.trim();
|
|
59
|
+
if (!trimmed) return;
|
|
60
|
+
|
|
61
|
+
// Parse command
|
|
62
|
+
const parts = trimmed.split(/\s+/);
|
|
63
|
+
const cmd = parts[0].toLowerCase();
|
|
64
|
+
const args = parts.slice(1);
|
|
65
|
+
const rawArgs = trimmed.slice(cmd.length).trim();
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Bang commands
|
|
69
|
+
if (bangs.isBang(cmd)) {
|
|
70
|
+
return await this.handleBang(trimmed);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
switch (cmd) {
|
|
74
|
+
// === SEARCH ===
|
|
75
|
+
case 'search':
|
|
76
|
+
case 's':
|
|
77
|
+
return await this.handleSearch(rawArgs, args);
|
|
78
|
+
|
|
79
|
+
case 'image':
|
|
80
|
+
return await this.handleImageSearch(rawArgs);
|
|
81
|
+
|
|
82
|
+
// === NAVIGATION ===
|
|
83
|
+
case 'open':
|
|
84
|
+
case 'o':
|
|
85
|
+
case 'goto':
|
|
86
|
+
return await this.handleOpen(rawArgs);
|
|
87
|
+
|
|
88
|
+
case 'link':
|
|
89
|
+
case 'l':
|
|
90
|
+
return await this.handleLink(parseInt(args[0]));
|
|
91
|
+
|
|
92
|
+
case 'back':
|
|
93
|
+
case 'b':
|
|
94
|
+
return this.handleBack();
|
|
95
|
+
|
|
96
|
+
case 'forward':
|
|
97
|
+
case 'f':
|
|
98
|
+
return this.handleForward();
|
|
99
|
+
|
|
100
|
+
case 'home':
|
|
101
|
+
return this.handleHome();
|
|
102
|
+
|
|
103
|
+
case 'refresh':
|
|
104
|
+
case 'reload':
|
|
105
|
+
return await this.handleRefresh();
|
|
106
|
+
|
|
107
|
+
// === READER ===
|
|
108
|
+
case 'reader':
|
|
109
|
+
case 'read':
|
|
110
|
+
return this.handleReader();
|
|
111
|
+
|
|
112
|
+
case 'info':
|
|
113
|
+
return this.handleInfo();
|
|
114
|
+
|
|
115
|
+
// === TABS ===
|
|
116
|
+
case 'tab':
|
|
117
|
+
return this.handleTab(args);
|
|
118
|
+
|
|
119
|
+
case 'tabs':
|
|
120
|
+
return this.handleTabsList();
|
|
121
|
+
|
|
122
|
+
// === HISTORY ===
|
|
123
|
+
case 'history':
|
|
124
|
+
case 'hist':
|
|
125
|
+
return this.handleHistory(args);
|
|
126
|
+
|
|
127
|
+
// === BOOKMARKS ===
|
|
128
|
+
case 'bookmark':
|
|
129
|
+
case 'bm':
|
|
130
|
+
return this.handleBookmark(args);
|
|
131
|
+
|
|
132
|
+
// === DEVELOPER ===
|
|
133
|
+
case 'inspect':
|
|
134
|
+
return this.handleInspect();
|
|
135
|
+
|
|
136
|
+
case 'headers':
|
|
137
|
+
return await this.handleHeaders();
|
|
138
|
+
|
|
139
|
+
case 'cookies':
|
|
140
|
+
return this.handleCookies();
|
|
141
|
+
|
|
142
|
+
case 'scan':
|
|
143
|
+
return await this.handleScan(rawArgs);
|
|
144
|
+
|
|
145
|
+
case 'robots':
|
|
146
|
+
return await this.handleRobots();
|
|
147
|
+
|
|
148
|
+
case 'sitemap':
|
|
149
|
+
return await this.handleSitemap();
|
|
150
|
+
|
|
151
|
+
// === DOWNLOADS ===
|
|
152
|
+
case 'download':
|
|
153
|
+
case 'dl':
|
|
154
|
+
return await this.handleDownload(rawArgs);
|
|
155
|
+
|
|
156
|
+
case 'downloads':
|
|
157
|
+
return this.handleDownloadsList();
|
|
158
|
+
|
|
159
|
+
// === NETWORK ===
|
|
160
|
+
case 'proxy':
|
|
161
|
+
return this.handleProxy(args);
|
|
162
|
+
|
|
163
|
+
case 'ua':
|
|
164
|
+
return this.handleUserAgent(args[0]);
|
|
165
|
+
|
|
166
|
+
// === THEME ===
|
|
167
|
+
case 'theme':
|
|
168
|
+
return this.handleTheme(args[0]);
|
|
169
|
+
|
|
170
|
+
case 'color':
|
|
171
|
+
return this.handleColor(args);
|
|
172
|
+
|
|
173
|
+
case 'style':
|
|
174
|
+
return this.handleStyle(args[0]);
|
|
175
|
+
|
|
176
|
+
// === SESSIONS ===
|
|
177
|
+
case 'login':
|
|
178
|
+
return this.handleLogin(args[0]);
|
|
179
|
+
|
|
180
|
+
case 'session':
|
|
181
|
+
return this.handleSession(args);
|
|
182
|
+
|
|
183
|
+
// === AI ===
|
|
184
|
+
case 'summary':
|
|
185
|
+
return this.handleSummary();
|
|
186
|
+
|
|
187
|
+
case 'ask':
|
|
188
|
+
return this.handleAsk(rawArgs);
|
|
189
|
+
|
|
190
|
+
case 'translate':
|
|
191
|
+
return this.handleTranslate(rawArgs);
|
|
192
|
+
|
|
193
|
+
// === STATS ===
|
|
194
|
+
case 'stats':
|
|
195
|
+
return this.handleStats();
|
|
196
|
+
|
|
197
|
+
// === PLUGINS ===
|
|
198
|
+
case 'plugin':
|
|
199
|
+
return this.handlePlugin(args);
|
|
200
|
+
|
|
201
|
+
// === MISC ===
|
|
202
|
+
case 'help':
|
|
203
|
+
case 'h':
|
|
204
|
+
case '?':
|
|
205
|
+
return this.handleHelp(args[0]);
|
|
206
|
+
|
|
207
|
+
case 'clear':
|
|
208
|
+
case 'cls':
|
|
209
|
+
console.clear();
|
|
210
|
+
return;
|
|
211
|
+
|
|
212
|
+
case 'exit':
|
|
213
|
+
case 'quit':
|
|
214
|
+
case 'q':
|
|
215
|
+
return this.handleExit();
|
|
216
|
+
|
|
217
|
+
case 'version':
|
|
218
|
+
case 'ver':
|
|
219
|
+
return ui.info('CLI Browser v1.0.0');
|
|
220
|
+
|
|
221
|
+
default:
|
|
222
|
+
// If it looks like a URL
|
|
223
|
+
if (trimmed.startsWith('http://') || trimmed.startsWith('https://') || trimmed.includes('.') && !trimmed.includes(' ')) {
|
|
224
|
+
return await this.handleOpen(trimmed);
|
|
225
|
+
}
|
|
226
|
+
// Otherwise treat as search
|
|
227
|
+
if (trimmed.length > 0) {
|
|
228
|
+
return await this.handleSearch(trimmed, parts);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch (err) {
|
|
232
|
+
ui.error(err.message || 'An error occurred');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ==================
|
|
237
|
+
// SEARCH
|
|
238
|
+
// ==================
|
|
239
|
+
async handleSearch(rawArgs, args) {
|
|
240
|
+
let query = rawArgs;
|
|
241
|
+
let engine = null;
|
|
242
|
+
let type = 'general';
|
|
243
|
+
let time = null;
|
|
244
|
+
let multi = false;
|
|
245
|
+
|
|
246
|
+
// Parse flags
|
|
247
|
+
for (let i = 0; i < args.length; i++) {
|
|
248
|
+
if (args[i] === '--engine' && args[i + 1]) {
|
|
249
|
+
engine = args[i + 1];
|
|
250
|
+
query = query.replace(`--engine ${engine}`, '').trim();
|
|
251
|
+
i++;
|
|
252
|
+
} else if (args[i] === '--type' && args[i + 1]) {
|
|
253
|
+
type = args[i + 1];
|
|
254
|
+
query = query.replace(`--type ${type}`, '').trim();
|
|
255
|
+
i++;
|
|
256
|
+
} else if (args[i] === '--time' && args[i + 1]) {
|
|
257
|
+
time = args[i + 1];
|
|
258
|
+
query = query.replace(`--time ${time}`, '').trim();
|
|
259
|
+
i++;
|
|
260
|
+
} else if (args[i] === '--multi') {
|
|
261
|
+
multi = true;
|
|
262
|
+
query = query.replace('--multi', '').trim();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!query) {
|
|
267
|
+
return ui.error('Usage: search <query> [--engine google] [--type image] [--time day]');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (multi) {
|
|
271
|
+
return await this.handleMultiSearch(query);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const spinner = ora({
|
|
275
|
+
text: theme.muted(`Searching "${query}"...`),
|
|
276
|
+
spinner: 'dots',
|
|
277
|
+
color: 'cyan',
|
|
278
|
+
}).start();
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const data = await search.search(query, { engine, type, time });
|
|
282
|
+
spinner.stop();
|
|
283
|
+
|
|
284
|
+
stats.incrementSearch();
|
|
285
|
+
history.add({ type: 'search', query, title: `Search: ${query}` });
|
|
286
|
+
|
|
287
|
+
if (data.results.length === 0) {
|
|
288
|
+
return ui.warn('No results found');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
ui.header(`Search Results: "${query}"`);
|
|
292
|
+
if (data.number_of_results) {
|
|
293
|
+
ui.info(`${data.number_of_results.toLocaleString()} results found`);
|
|
294
|
+
}
|
|
295
|
+
console.log();
|
|
296
|
+
|
|
297
|
+
data.results.slice(0, config.get('pageSize')).forEach((r, i) => {
|
|
298
|
+
ui.searchResult(i + 1, r);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (data.suggestions?.length) {
|
|
302
|
+
console.log();
|
|
303
|
+
ui.subheader('Suggestions:');
|
|
304
|
+
data.suggestions.forEach(s => {
|
|
305
|
+
console.log(' ' + theme.muted('→ ') + theme.info(s));
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (data.infoboxes?.length) {
|
|
310
|
+
console.log();
|
|
311
|
+
const ib = data.infoboxes[0];
|
|
312
|
+
ui.subheader(ib.infobox || 'Info');
|
|
313
|
+
if (ib.content) {
|
|
314
|
+
console.log(' ' + theme.muted(ib.content.slice(0, 200)));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
this.lastSearchResults = data.results;
|
|
319
|
+
console.log();
|
|
320
|
+
ui.info('Type "open <number>" to visit a result');
|
|
321
|
+
|
|
322
|
+
} catch (err) {
|
|
323
|
+
spinner.stop();
|
|
324
|
+
ui.error(`Search failed: ${err.message}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async handleMultiSearch(query) {
|
|
329
|
+
const spinner = ora({
|
|
330
|
+
text: theme.muted(`Multi-searching "${query}"...`),
|
|
331
|
+
spinner: 'dots',
|
|
332
|
+
color: 'cyan',
|
|
333
|
+
}).start();
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const results = await search.multiSearch(query);
|
|
337
|
+
spinner.stop();
|
|
338
|
+
|
|
339
|
+
stats.incrementSearch();
|
|
340
|
+
ui.header(`Multi-Search: "${query}"`);
|
|
341
|
+
|
|
342
|
+
for (const [category, items] of Object.entries(results)) {
|
|
343
|
+
if (items.length > 0) {
|
|
344
|
+
ui.subheader(`${category.charAt(0).toUpperCase() + category.slice(1)}`);
|
|
345
|
+
items.forEach((r, i) => {
|
|
346
|
+
ui.searchResult(i + 1, r);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
spinner.stop();
|
|
352
|
+
ui.error(`Multi-search failed: ${err.message}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async handleImageSearch(query) {
|
|
357
|
+
if (!query) return ui.error('Usage: image <query>');
|
|
358
|
+
|
|
359
|
+
const spinner = ora({
|
|
360
|
+
text: theme.muted(`Searching images "${query}"...`),
|
|
361
|
+
spinner: 'dots',
|
|
362
|
+
color: 'cyan',
|
|
363
|
+
}).start();
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const data = await search.imageSearch(query);
|
|
367
|
+
spinner.stop();
|
|
368
|
+
|
|
369
|
+
stats.incrementSearch();
|
|
370
|
+
ui.header(`Image Results: "${query}"`);
|
|
371
|
+
|
|
372
|
+
data.results.slice(0, 10).forEach((r, i) => {
|
|
373
|
+
const num = theme.accent(` [${i + 1}] `);
|
|
374
|
+
console.log(num + theme.title(r.title || 'Image'));
|
|
375
|
+
if (r.img_src) console.log(' ' + theme.url(r.img_src));
|
|
376
|
+
if (r.url) console.log(' ' + theme.muted('from: ' + r.url));
|
|
377
|
+
console.log();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
this.lastSearchResults = data.results;
|
|
381
|
+
ui.info('Type "open <number>" to view source page');
|
|
382
|
+
} catch (err) {
|
|
383
|
+
spinner.stop();
|
|
384
|
+
ui.error(`Image search failed: ${err.message}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ==================
|
|
389
|
+
// OPEN / NAVIGATION
|
|
390
|
+
// ==================
|
|
391
|
+
async handleOpen(target) {
|
|
392
|
+
let url = target;
|
|
393
|
+
|
|
394
|
+
// If it's a number, get from search results
|
|
395
|
+
const num = parseInt(target);
|
|
396
|
+
if (!isNaN(num) && num > 0) {
|
|
397
|
+
const result = search.getResultByIndex(num);
|
|
398
|
+
if (!result) {
|
|
399
|
+
return ui.error(`No result #${num}. Run a search first.`);
|
|
400
|
+
}
|
|
401
|
+
url = result.url;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Add protocol if missing
|
|
405
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
406
|
+
url = 'https://' + url;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
const page = await this.fetchPage(url);
|
|
411
|
+
const rendered = renderer.render(page.html, page.url);
|
|
412
|
+
|
|
413
|
+
tabs.setCurrentPage(page.url, rendered.title, page.html, rendered.content, rendered.links);
|
|
414
|
+
this.currentPage = page;
|
|
415
|
+
|
|
416
|
+
ui.statusBar(tabs.getAllTabs(), tabs.getActiveIndex(), page.url);
|
|
417
|
+
ui.pageView(rendered.title, rendered.content, rendered.links.slice(0, 30));
|
|
418
|
+
} catch (err) {
|
|
419
|
+
ui.error(`Failed to load: ${err.message}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async handleLink(idx) {
|
|
424
|
+
if (!idx || isNaN(idx)) {
|
|
425
|
+
return ui.error('Usage: link <number>');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const tab = tabs.getCurrentTab();
|
|
429
|
+
const link = tab.links[idx - 1];
|
|
430
|
+
if (!link) {
|
|
431
|
+
return ui.error(`Link #${idx} not found`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
await this.handleOpen(link.url);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
handleBack() {
|
|
438
|
+
try {
|
|
439
|
+
const tab = tabs.goBack();
|
|
440
|
+
if (tab.content) {
|
|
441
|
+
ui.statusBar(tabs.getAllTabs(), tabs.getActiveIndex(), tab.url);
|
|
442
|
+
ui.pageView(tab.title, tab.content, tab.links?.slice(0, 30));
|
|
443
|
+
}
|
|
444
|
+
} catch (err) {
|
|
445
|
+
ui.error(err.message);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
handleForward() {
|
|
450
|
+
try {
|
|
451
|
+
const tab = tabs.goForward();
|
|
452
|
+
if (tab.content) {
|
|
453
|
+
ui.statusBar(tabs.getAllTabs(), tabs.getActiveIndex(), tab.url);
|
|
454
|
+
ui.pageView(tab.title, tab.content, tab.links?.slice(0, 30));
|
|
455
|
+
}
|
|
456
|
+
} catch (err) {
|
|
457
|
+
ui.error(err.message);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
handleHome() {
|
|
462
|
+
console.clear();
|
|
463
|
+
ui.banner();
|
|
464
|
+
ui.info('Welcome home! Type "search <query>" or "help" for commands.');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async handleRefresh() {
|
|
468
|
+
const tab = tabs.getCurrentTab();
|
|
469
|
+
if (tab.url) {
|
|
470
|
+
await this.handleOpen(tab.url);
|
|
471
|
+
} else {
|
|
472
|
+
ui.warn('No page to refresh');
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ==================
|
|
477
|
+
// READER / INFO
|
|
478
|
+
// ==================
|
|
479
|
+
handleReader() {
|
|
480
|
+
const tab = tabs.getCurrentTab();
|
|
481
|
+
if (!tab.html) {
|
|
482
|
+
return ui.warn('No page loaded. Open a page first.');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const rendered = renderer.renderReader(tab.html, tab.url);
|
|
486
|
+
ui.header(`Reader Mode: ${rendered.title}`);
|
|
487
|
+
console.log(rendered.content);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
handleInfo() {
|
|
491
|
+
const tab = tabs.getCurrentTab();
|
|
492
|
+
if (!tab.html) {
|
|
493
|
+
return ui.warn('No page loaded. Open a page first.');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const info = renderer.getPageInfo(tab.html);
|
|
497
|
+
ui.header('Page Information');
|
|
498
|
+
Object.entries(info).forEach(([key, value]) => {
|
|
499
|
+
if (value) {
|
|
500
|
+
ui.keyValue(key, typeof value === 'string' ? value.slice(0, 80) : String(value));
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ==================
|
|
506
|
+
// TABS
|
|
507
|
+
// ==================
|
|
508
|
+
handleTab(args) {
|
|
509
|
+
const action = args[0];
|
|
510
|
+
const param = args[1];
|
|
511
|
+
|
|
512
|
+
switch (action) {
|
|
513
|
+
case 'new':
|
|
514
|
+
tabs.newTab(param || 'New Tab');
|
|
515
|
+
ui.success('New tab created');
|
|
516
|
+
this.handleTabsList();
|
|
517
|
+
break;
|
|
518
|
+
case 'close':
|
|
519
|
+
try {
|
|
520
|
+
const idx = param ? parseInt(param) - 1 : undefined;
|
|
521
|
+
tabs.closeTab(idx);
|
|
522
|
+
ui.success('Tab closed');
|
|
523
|
+
this.handleTabsList();
|
|
524
|
+
} catch (err) {
|
|
525
|
+
ui.error(err.message);
|
|
526
|
+
}
|
|
527
|
+
break;
|
|
528
|
+
case 'switch':
|
|
529
|
+
if (!param) return ui.error('Usage: tab switch <number>');
|
|
530
|
+
try {
|
|
531
|
+
const tab = tabs.switchTab(parseInt(param) - 1);
|
|
532
|
+
ui.success(`Switched to tab ${param}`);
|
|
533
|
+
if (tab.content) {
|
|
534
|
+
ui.statusBar(tabs.getAllTabs(), tabs.getActiveIndex(), tab.url);
|
|
535
|
+
ui.pageView(tab.title, tab.content, tab.links?.slice(0, 30));
|
|
536
|
+
}
|
|
537
|
+
} catch (err) {
|
|
538
|
+
ui.error(err.message);
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
default:
|
|
542
|
+
ui.error('Usage: tab <new|close|switch> [number]');
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
handleTabsList() {
|
|
547
|
+
const allTabs = tabs.getAllTabs();
|
|
548
|
+
ui.header('Tabs');
|
|
549
|
+
allTabs.forEach(tab => {
|
|
550
|
+
const marker = tab.active ? theme.accent(' ● ') : theme.muted(' ○ ');
|
|
551
|
+
const title = tab.active ? theme.title(tab.title) : theme.secondary(tab.title);
|
|
552
|
+
const url = tab.url ? theme.url(` - ${tab.url}`) : '';
|
|
553
|
+
console.log(` ${marker} ${theme.accent(`[${tab.index}]`)} ${title}${url}`);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ==================
|
|
558
|
+
// HISTORY
|
|
559
|
+
// ==================
|
|
560
|
+
handleHistory(args) {
|
|
561
|
+
const action = args[0];
|
|
562
|
+
|
|
563
|
+
if (action === 'clear') {
|
|
564
|
+
history.clear();
|
|
565
|
+
return ui.success('History cleared');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (action === 'open' && args[1]) {
|
|
569
|
+
const entry = history.getByIndex(parseInt(args[1]));
|
|
570
|
+
if (entry) {
|
|
571
|
+
if (entry.url) {
|
|
572
|
+
return this.handleOpen(entry.url);
|
|
573
|
+
}
|
|
574
|
+
if (entry.query) {
|
|
575
|
+
return this.handleSearch(entry.query, entry.query.split(/\s+/));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return ui.error('History entry not found');
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const items = history.getRecent(20);
|
|
582
|
+
if (items.length === 0) {
|
|
583
|
+
return ui.info('No history yet');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
ui.header('History');
|
|
587
|
+
items.forEach((item, i) => {
|
|
588
|
+
const time = new Date(item.timestamp).toLocaleTimeString();
|
|
589
|
+
const icon = item.type === 'search' ? '🔍' : '📄';
|
|
590
|
+
console.log(
|
|
591
|
+
theme.accent(` [${i + 1}] `) +
|
|
592
|
+
theme.muted(`${time} `) +
|
|
593
|
+
`${icon} ` +
|
|
594
|
+
theme.secondary(item.title || item.query || item.url)
|
|
595
|
+
);
|
|
596
|
+
});
|
|
597
|
+
console.log();
|
|
598
|
+
ui.info('Type "history open <number>" to revisit');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// ==================
|
|
602
|
+
// BOOKMARKS
|
|
603
|
+
// ==================
|
|
604
|
+
handleBookmark(args) {
|
|
605
|
+
const action = args[0];
|
|
606
|
+
|
|
607
|
+
switch (action) {
|
|
608
|
+
case 'add': {
|
|
609
|
+
const tab = tabs.getCurrentTab();
|
|
610
|
+
if (!tab.url) {
|
|
611
|
+
return ui.warn('No page loaded to bookmark');
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
bookmarks.add(tab.title, tab.url);
|
|
615
|
+
ui.success(`Bookmarked: ${tab.title}`);
|
|
616
|
+
} catch (err) {
|
|
617
|
+
ui.warn(err.message);
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
case 'list':
|
|
622
|
+
case undefined: {
|
|
623
|
+
const all = bookmarks.getAll();
|
|
624
|
+
if (all.length === 0) {
|
|
625
|
+
return ui.info('No bookmarks');
|
|
626
|
+
}
|
|
627
|
+
ui.header('Bookmarks');
|
|
628
|
+
all.forEach((bm, i) => {
|
|
629
|
+
console.log(
|
|
630
|
+
theme.accent(` [${i + 1}] `) +
|
|
631
|
+
theme.title(bm.title) + '\n' +
|
|
632
|
+
' ' + theme.url(bm.url)
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
console.log();
|
|
636
|
+
ui.info('Type "bookmark open <number>" to visit');
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
case 'open': {
|
|
640
|
+
const bm = bookmarks.getByIndex(parseInt(args[1]));
|
|
641
|
+
if (bm) {
|
|
642
|
+
return this.handleOpen(bm.url);
|
|
643
|
+
}
|
|
644
|
+
ui.error('Bookmark not found');
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
case 'remove':
|
|
648
|
+
case 'rm':
|
|
649
|
+
case 'delete': {
|
|
650
|
+
try {
|
|
651
|
+
const removed = bookmarks.remove(parseInt(args[1]));
|
|
652
|
+
ui.success(`Removed: ${removed.title}`);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
ui.error(err.message);
|
|
655
|
+
}
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
default:
|
|
659
|
+
ui.error('Usage: bookmark <add|list|open|remove> [number]');
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// ==================
|
|
664
|
+
// DEVELOPER
|
|
665
|
+
// ==================
|
|
666
|
+
handleInspect() {
|
|
667
|
+
const tab = tabs.getCurrentTab();
|
|
668
|
+
if (!tab.html) {
|
|
669
|
+
return ui.warn('No page loaded');
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const data = developer.inspect(tab.html, tab.url);
|
|
673
|
+
ui.header('Site Inspection');
|
|
674
|
+
|
|
675
|
+
ui.subheader('Frameworks');
|
|
676
|
+
data.framework.forEach(f => console.log(' ' + theme.accent(' → ') + theme.secondary(f)));
|
|
677
|
+
|
|
678
|
+
if (data.technologies.length) {
|
|
679
|
+
ui.subheader('Technologies');
|
|
680
|
+
data.technologies.forEach(t => console.log(' ' + theme.accent(' → ') + theme.secondary(t)));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (data.scripts.length) {
|
|
684
|
+
ui.subheader('Scripts');
|
|
685
|
+
data.scripts.slice(0, 10).forEach(s => console.log(' ' + theme.muted(' → ') + theme.url(s)));
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (Object.keys(data.metaTags).length) {
|
|
689
|
+
ui.subheader('Meta Tags');
|
|
690
|
+
Object.entries(data.metaTags).slice(0, 15).forEach(([k, v]) => {
|
|
691
|
+
ui.keyValue(k, v.slice(0, 60));
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async handleHeaders() {
|
|
697
|
+
const tab = tabs.getCurrentTab();
|
|
698
|
+
if (!tab.url) return ui.warn('No page loaded');
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
const data = await developer.getHeaders(tab.url);
|
|
702
|
+
ui.header(`HTTP Headers (${data.status} ${data.statusText})`);
|
|
703
|
+
Object.entries(data.headers).forEach(([k, v]) => {
|
|
704
|
+
ui.keyValue(k, String(v).slice(0, 80));
|
|
705
|
+
});
|
|
706
|
+
} catch (err) {
|
|
707
|
+
ui.error(err.message);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
handleCookies() {
|
|
712
|
+
const tab = tabs.getCurrentTab();
|
|
713
|
+
if (!this.currentPage) return ui.warn('No page loaded');
|
|
714
|
+
|
|
715
|
+
const cookies = developer.getCookies(tab.html, this.currentPage.headers);
|
|
716
|
+
ui.header('Cookies');
|
|
717
|
+
if (cookies.length === 0) {
|
|
718
|
+
return ui.info('No cookies found');
|
|
719
|
+
}
|
|
720
|
+
cookies.forEach(c => {
|
|
721
|
+
ui.keyValue(c.name, c.value?.slice(0, 60) || '');
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async handleScan(target) {
|
|
726
|
+
if (!target) {
|
|
727
|
+
const tab = tabs.getCurrentTab();
|
|
728
|
+
if (!tab.url) return ui.error('Usage: scan <url>');
|
|
729
|
+
target = tab.url;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (!target.startsWith('http')) target = 'https://' + target;
|
|
733
|
+
|
|
734
|
+
const spinner = ora({
|
|
735
|
+
text: theme.muted(`Scanning ${target}...`),
|
|
736
|
+
spinner: 'dots',
|
|
737
|
+
color: 'cyan',
|
|
738
|
+
}).start();
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
const data = await developer.scan(target);
|
|
742
|
+
spinner.stop();
|
|
743
|
+
|
|
744
|
+
ui.header(`Scan: ${target}`);
|
|
745
|
+
|
|
746
|
+
if (data.security) {
|
|
747
|
+
ui.subheader('Security');
|
|
748
|
+
ui.keyValue('HTTPS', data.security.https ? '✓ Yes' : '✗ No');
|
|
749
|
+
ui.keyValue('HSTS', data.security.hsts ? '✓ Enabled' : '✗ Not set');
|
|
750
|
+
ui.keyValue('CSP', data.security.csp ? '✓ Enabled' : '✗ Not set');
|
|
751
|
+
ui.keyValue('X-Frame-Options', data.security.xframe);
|
|
752
|
+
ui.keyValue('X-Content-Type', data.security.xcontent);
|
|
753
|
+
ui.keyValue('Server', data.security.server);
|
|
754
|
+
ui.keyValue('Powered By', data.security.poweredBy);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (data.frameworks?.length) {
|
|
758
|
+
ui.subheader('Frameworks');
|
|
759
|
+
data.frameworks.forEach(f => console.log(' ' + theme.accent(' → ') + f));
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (data.technologies?.length) {
|
|
763
|
+
ui.subheader('Technologies');
|
|
764
|
+
data.technologies.forEach(t => console.log(' ' + theme.accent(' → ') + t));
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (data.links?.length) {
|
|
768
|
+
ui.subheader(`Links (${data.links.length})`);
|
|
769
|
+
data.links.slice(0, 15).forEach(l => {
|
|
770
|
+
console.log(' ' + theme.muted(' → ') + theme.url(l));
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
} catch (err) {
|
|
774
|
+
spinner.stop();
|
|
775
|
+
ui.error(err.message);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async handleRobots() {
|
|
780
|
+
const tab = tabs.getCurrentTab();
|
|
781
|
+
if (!tab.url) return ui.error('Open a page first');
|
|
782
|
+
|
|
783
|
+
const spinner = ora({ text: theme.muted('Fetching robots.txt...'), spinner: 'dots', color: 'cyan' }).start();
|
|
784
|
+
try {
|
|
785
|
+
const content = await developer.getRobots(tab.url);
|
|
786
|
+
spinner.stop();
|
|
787
|
+
ui.header('robots.txt');
|
|
788
|
+
console.log(theme.secondary(content));
|
|
789
|
+
} catch (err) {
|
|
790
|
+
spinner.stop();
|
|
791
|
+
ui.error(err.message);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
async handleSitemap() {
|
|
796
|
+
const tab = tabs.getCurrentTab();
|
|
797
|
+
if (!tab.url) return ui.error('Open a page first');
|
|
798
|
+
|
|
799
|
+
const spinner = ora({ text: theme.muted('Fetching sitemap.xml...'), spinner: 'dots', color: 'cyan' }).start();
|
|
800
|
+
try {
|
|
801
|
+
const data = await developer.getSitemap(tab.url);
|
|
802
|
+
spinner.stop();
|
|
803
|
+
ui.header('Sitemap');
|
|
804
|
+
if (Array.isArray(data)) {
|
|
805
|
+
data.slice(0, 30).forEach((url, i) => {
|
|
806
|
+
console.log(theme.accent(` [${i + 1}] `) + theme.url(url));
|
|
807
|
+
});
|
|
808
|
+
if (data.length > 30) {
|
|
809
|
+
ui.muted(` ... and ${data.length - 30} more`);
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
console.log(theme.secondary(data));
|
|
813
|
+
}
|
|
814
|
+
} catch (err) {
|
|
815
|
+
spinner.stop();
|
|
816
|
+
ui.error(err.message);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ==================
|
|
821
|
+
// DOWNLOADS
|
|
822
|
+
// ==================
|
|
823
|
+
async handleDownload(url) {
|
|
824
|
+
if (!url) return ui.error('Usage: download <url>');
|
|
825
|
+
if (!url.startsWith('http')) url = 'https://' + url;
|
|
826
|
+
|
|
827
|
+
const spinner = ora({ text: theme.muted('Downloading...'), spinner: 'dots', color: 'cyan' }).start();
|
|
828
|
+
try {
|
|
829
|
+
const entry = await downloads.download(url);
|
|
830
|
+
spinner.stop();
|
|
831
|
+
ui.success(`Downloaded: ${entry.filename} (${downloads.formatSize(entry.downloaded)})`);
|
|
832
|
+
ui.info(`Saved to: ${entry.filepath}`);
|
|
833
|
+
} catch (err) {
|
|
834
|
+
spinner.stop();
|
|
835
|
+
ui.error(`Download failed: ${err.message}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
handleDownloadsList() {
|
|
840
|
+
const dls = downloads.getDownloads();
|
|
841
|
+
if (dls.length === 0) return ui.info('No downloads');
|
|
842
|
+
|
|
843
|
+
ui.header('Downloads');
|
|
844
|
+
dls.forEach(dl => {
|
|
845
|
+
const status = dl.status === 'complete' ? theme.success('✓') :
|
|
846
|
+
dl.status === 'failed' ? theme.error('✗') :
|
|
847
|
+
theme.warning('↓');
|
|
848
|
+
console.log(
|
|
849
|
+
` ${status} ${theme.accent(`[${dl.id}]`)} ${theme.secondary(dl.filename)} ` +
|
|
850
|
+
theme.muted(`(${downloads.formatSize(dl.downloaded)})`)
|
|
851
|
+
);
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// ==================
|
|
856
|
+
// NETWORK
|
|
857
|
+
// ==================
|
|
858
|
+
handleProxy(args) {
|
|
859
|
+
if (args[0] === 'set' && args[1]) {
|
|
860
|
+
network.setProxy(args[1]);
|
|
861
|
+
ui.success(`Proxy set to ${args[1]}`);
|
|
862
|
+
} else if (args[0] === 'clear') {
|
|
863
|
+
network.clearProxy();
|
|
864
|
+
ui.success('Proxy cleared');
|
|
865
|
+
} else {
|
|
866
|
+
const proxy = network.getProxy();
|
|
867
|
+
if (proxy) {
|
|
868
|
+
ui.info(`Current proxy: ${proxy}`);
|
|
869
|
+
} else {
|
|
870
|
+
ui.info('No proxy set');
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
handleUserAgent(preset) {
|
|
876
|
+
if (!preset) {
|
|
877
|
+
return ui.info(`Current UA: ${network.getUserAgent()}`);
|
|
878
|
+
}
|
|
879
|
+
const ua = network.setUserAgent(preset);
|
|
880
|
+
ui.success(`User-Agent set to: ${ua.slice(0, 60)}...`);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// ==================
|
|
884
|
+
// THEME
|
|
885
|
+
// ==================
|
|
886
|
+
handleTheme(name) {
|
|
887
|
+
if (!name) {
|
|
888
|
+
ui.header('Themes');
|
|
889
|
+
theme.listThemes().forEach(t => {
|
|
890
|
+
const active = t === theme.currentTheme ? theme.accent(' ● ') : ' ';
|
|
891
|
+
console.log(` ${active}${theme.secondary(t)}`);
|
|
892
|
+
});
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (theme.setTheme(name)) {
|
|
897
|
+
ui.success(`Theme set to "${name}"`);
|
|
898
|
+
} else {
|
|
899
|
+
ui.error(`Unknown theme. Available: ${theme.listThemes().join(', ')}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
handleColor(args) {
|
|
904
|
+
if (args.length < 2) {
|
|
905
|
+
return ui.error('Usage: color <element> <color>');
|
|
906
|
+
}
|
|
907
|
+
const colors = config.get('colors');
|
|
908
|
+
colors[args[0]] = args[1];
|
|
909
|
+
config.set('colors', colors);
|
|
910
|
+
ui.success(`${args[0]} color set to ${args[1]}`);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
handleStyle(styleName) {
|
|
914
|
+
if (!styleName) {
|
|
915
|
+
return ui.info(`Current style: ${config.get('style')}`);
|
|
916
|
+
}
|
|
917
|
+
config.set('style', styleName);
|
|
918
|
+
ui.success(`Style set to "${styleName}"`);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// ==================
|
|
922
|
+
// SESSIONS
|
|
923
|
+
// ==================
|
|
924
|
+
handleLogin(domain) {
|
|
925
|
+
if (!domain) {
|
|
926
|
+
const tab = tabs.getCurrentTab();
|
|
927
|
+
if (!tab.url) return ui.error('Usage: login <domain>');
|
|
928
|
+
domain = new URL(tab.url).hostname;
|
|
929
|
+
}
|
|
930
|
+
if (this.currentPage?.headers) {
|
|
931
|
+
const cookies = developer.getCookies('', this.currentPage.headers);
|
|
932
|
+
sessions.saveSession(domain, cookies);
|
|
933
|
+
ui.success(`Session saved for ${domain}`);
|
|
934
|
+
} else {
|
|
935
|
+
ui.warn('No cookies to save');
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
handleSession(args) {
|
|
940
|
+
const action = args[0];
|
|
941
|
+
|
|
942
|
+
switch (action) {
|
|
943
|
+
case 'list': {
|
|
944
|
+
const domains = sessions.listDomains();
|
|
945
|
+
if (domains.length === 0) return ui.info('No saved sessions');
|
|
946
|
+
ui.header('Sessions');
|
|
947
|
+
domains.forEach((d, i) => {
|
|
948
|
+
console.log(theme.accent(` [${i + 1}] `) + theme.secondary(d));
|
|
949
|
+
});
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
case 'clear':
|
|
953
|
+
sessions.clearAll();
|
|
954
|
+
ui.success('All sessions cleared');
|
|
955
|
+
break;
|
|
956
|
+
default:
|
|
957
|
+
ui.error('Usage: session <list|clear>');
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// ==================
|
|
962
|
+
// AI
|
|
963
|
+
// ==================
|
|
964
|
+
handleSummary() {
|
|
965
|
+
const tab = tabs.getCurrentTab();
|
|
966
|
+
if (!tab.content) return ui.warn('No page loaded');
|
|
967
|
+
|
|
968
|
+
// Extract key content for summary
|
|
969
|
+
const text = tab.content
|
|
970
|
+
.replace(/\x1B\[[0-9;]*m/g, '') // strip ANSI
|
|
971
|
+
.replace(/\s+/g, ' ')
|
|
972
|
+
.trim();
|
|
973
|
+
|
|
974
|
+
const sentences = text.match(/[^.!?]+[.!?]+/g) || [];
|
|
975
|
+
const summary = sentences.slice(0, 8).join(' ').trim();
|
|
976
|
+
|
|
977
|
+
ui.header(`Summary: ${tab.title}`);
|
|
978
|
+
if (summary) {
|
|
979
|
+
console.log('\n ' + theme.secondary(summary.slice(0, 500)));
|
|
980
|
+
} else {
|
|
981
|
+
ui.warn('Could not generate summary from this page');
|
|
982
|
+
}
|
|
983
|
+
console.log();
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
handleAsk(question) {
|
|
987
|
+
if (!question) return ui.error('Usage: ask <question>');
|
|
988
|
+
|
|
989
|
+
const tab = tabs.getCurrentTab();
|
|
990
|
+
if (!tab.content) return ui.warn('No page loaded');
|
|
991
|
+
|
|
992
|
+
const text = tab.content.replace(/\x1B\[[0-9;]*m/g, '').replace(/\s+/g, ' ');
|
|
993
|
+
|
|
994
|
+
// Simple keyword extraction
|
|
995
|
+
const keywords = question.toLowerCase().split(/\s+/).filter(w => w.length > 3);
|
|
996
|
+
const sentences = text.match(/[^.!?]+[.!?]+/g) || [];
|
|
997
|
+
|
|
998
|
+
const relevant = sentences.filter(s => {
|
|
999
|
+
const lower = s.toLowerCase();
|
|
1000
|
+
return keywords.some(k => lower.includes(k));
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
ui.header('Answer');
|
|
1004
|
+
if (relevant.length > 0) {
|
|
1005
|
+
console.log('\n ' + theme.secondary(relevant.slice(0, 5).join(' ').trim().slice(0, 500)));
|
|
1006
|
+
} else {
|
|
1007
|
+
console.log('\n ' + theme.muted('No relevant information found on this page for your question.'));
|
|
1008
|
+
}
|
|
1009
|
+
console.log();
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
handleTranslate(targetLang) {
|
|
1013
|
+
if (!targetLang) return ui.error('Usage: translate <language>');
|
|
1014
|
+
ui.info(`Translation to "${targetLang}" requires an external API.`);
|
|
1015
|
+
ui.info('Connect an AI translation service for this feature.');
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// ==================
|
|
1019
|
+
// BANG COMMANDS
|
|
1020
|
+
// ==================
|
|
1021
|
+
async handleBang(input) {
|
|
1022
|
+
const { bang, query } = bangs.parse(input);
|
|
1023
|
+
const url = bangs.getUrl(bang, query);
|
|
1024
|
+
|
|
1025
|
+
if (!url) {
|
|
1026
|
+
ui.error(`Unknown bang command: ${bang}`);
|
|
1027
|
+
ui.info('Available bangs:');
|
|
1028
|
+
bangs.listBangs().forEach(b => {
|
|
1029
|
+
console.log(theme.accent(` ${b.bang.padEnd(10)}`) + theme.muted(b.name));
|
|
1030
|
+
});
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (!query) {
|
|
1035
|
+
return ui.error(`Usage: ${bang} <query>`);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
await this.handleOpen(url);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// ==================
|
|
1042
|
+
// STATS
|
|
1043
|
+
// ==================
|
|
1044
|
+
handleStats() {
|
|
1045
|
+
const s = stats.getStats();
|
|
1046
|
+
ui.header('Statistics');
|
|
1047
|
+
ui.keyValue('Searches', String(s.searchCount));
|
|
1048
|
+
ui.keyValue('Pages Visited', String(s.pagesVisited));
|
|
1049
|
+
ui.keyValue('Session Time', s.sessionTime);
|
|
1050
|
+
ui.keyValue('Total Time', s.totalTime);
|
|
1051
|
+
ui.keyValue('Bookmarks', String(s.bookmarkCount));
|
|
1052
|
+
ui.keyValue('History Items', String(s.historyCount));
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// ==================
|
|
1056
|
+
// PLUGINS
|
|
1057
|
+
// ==================
|
|
1058
|
+
handlePlugin(args) {
|
|
1059
|
+
const action = args[0];
|
|
1060
|
+
const name = args[1];
|
|
1061
|
+
|
|
1062
|
+
switch (action) {
|
|
1063
|
+
case 'install': {
|
|
1064
|
+
if (!name) return ui.error('Usage: plugin install <name>');
|
|
1065
|
+
try {
|
|
1066
|
+
const p = plugins.install(name);
|
|
1067
|
+
ui.success(`Installed plugin: ${p.name} v${p.version}`);
|
|
1068
|
+
ui.info(p.description);
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
ui.error(err.message);
|
|
1071
|
+
}
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
case 'remove':
|
|
1075
|
+
case 'uninstall': {
|
|
1076
|
+
if (!name) return ui.error('Usage: plugin remove <name>');
|
|
1077
|
+
try {
|
|
1078
|
+
plugins.uninstall(name);
|
|
1079
|
+
ui.success(`Removed plugin: ${name}`);
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
ui.error(err.message);
|
|
1082
|
+
}
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
case 'list':
|
|
1086
|
+
default: {
|
|
1087
|
+
const installed = plugins.listInstalled();
|
|
1088
|
+
if (installed.length === 0) {
|
|
1089
|
+
ui.info('No plugins installed');
|
|
1090
|
+
ui.info('Available: youtube, wikipedia, github, stackoverflow, reddit');
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
ui.header('Plugins');
|
|
1094
|
+
installed.forEach(p => {
|
|
1095
|
+
console.log(theme.accent(` → `) + theme.secondary(`${p.name}`) + theme.muted(` v${p.version} - ${p.description}`));
|
|
1096
|
+
});
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// ==================
|
|
1103
|
+
// HELP
|
|
1104
|
+
// ==================
|
|
1105
|
+
handleHelp(topic) {
|
|
1106
|
+
if (topic) {
|
|
1107
|
+
return this.handleHelpTopic(topic);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
console.log();
|
|
1111
|
+
ui.helpBox('Search', [
|
|
1112
|
+
['search <query>', 'Search the web'],
|
|
1113
|
+
['s <query>', 'Search (shortcut)'],
|
|
1114
|
+
['search <q> --engine google', 'Use specific search engine'],
|
|
1115
|
+
['search <q> --type image|video|news', 'Category search'],
|
|
1116
|
+
['search <q> --time day|week|month|year', 'Time-filtered search'],
|
|
1117
|
+
['search <q> --multi', 'Multi-category search'],
|
|
1118
|
+
['image <query>', 'Image search'],
|
|
1119
|
+
]);
|
|
1120
|
+
|
|
1121
|
+
ui.helpBox('Browse', [
|
|
1122
|
+
['open <number|url>', 'Open search result or URL'],
|
|
1123
|
+
['link <number>', 'Follow a link on current page'],
|
|
1124
|
+
['reader', 'Reader mode (clean view)'],
|
|
1125
|
+
['info', 'Page information'],
|
|
1126
|
+
]);
|
|
1127
|
+
|
|
1128
|
+
ui.helpBox('Navigation', [
|
|
1129
|
+
['back / b', 'Go back'],
|
|
1130
|
+
['forward / f', 'Go forward'],
|
|
1131
|
+
['home', 'Go home'],
|
|
1132
|
+
['refresh', 'Refresh page'],
|
|
1133
|
+
]);
|
|
1134
|
+
|
|
1135
|
+
ui.helpBox('Tabs', [
|
|
1136
|
+
['tab new', 'New tab'],
|
|
1137
|
+
['tab switch <n>', 'Switch to tab'],
|
|
1138
|
+
['tab close <n>', 'Close tab'],
|
|
1139
|
+
['tabs', 'List all tabs'],
|
|
1140
|
+
]);
|
|
1141
|
+
|
|
1142
|
+
ui.helpBox('History & Bookmarks', [
|
|
1143
|
+
['history', 'View history'],
|
|
1144
|
+
['history open <n>', 'Revisit from history'],
|
|
1145
|
+
['history clear', 'Clear history'],
|
|
1146
|
+
['bookmark add', 'Bookmark current page'],
|
|
1147
|
+
['bookmark list', 'List bookmarks'],
|
|
1148
|
+
['bookmark open <n>', 'Open bookmark'],
|
|
1149
|
+
['bookmark remove <n>', 'Remove bookmark'],
|
|
1150
|
+
]);
|
|
1151
|
+
|
|
1152
|
+
ui.helpBox('Bang Commands', [
|
|
1153
|
+
['!yt <query>', 'YouTube'],
|
|
1154
|
+
['!gh <query>', 'GitHub'],
|
|
1155
|
+
['!wiki <query>', 'Wikipedia'],
|
|
1156
|
+
['!npm <query>', 'npm'],
|
|
1157
|
+
['!so <query>', 'Stack Overflow'],
|
|
1158
|
+
['!reddit <query>', 'Reddit'],
|
|
1159
|
+
]);
|
|
1160
|
+
|
|
1161
|
+
ui.helpBox('Developer', [
|
|
1162
|
+
['inspect', 'Inspect site technologies'],
|
|
1163
|
+
['headers', 'View HTTP headers'],
|
|
1164
|
+
['cookies', 'View cookies'],
|
|
1165
|
+
['scan <url>', 'Security scan'],
|
|
1166
|
+
['robots', 'View robots.txt'],
|
|
1167
|
+
['sitemap', 'View sitemap'],
|
|
1168
|
+
]);
|
|
1169
|
+
|
|
1170
|
+
ui.helpBox('Tools', [
|
|
1171
|
+
['download <url>', 'Download a file'],
|
|
1172
|
+
['downloads', 'List downloads'],
|
|
1173
|
+
['summary', 'Summarize current page'],
|
|
1174
|
+
['ask <question>', 'Ask about current page'],
|
|
1175
|
+
['stats', 'View statistics'],
|
|
1176
|
+
]);
|
|
1177
|
+
|
|
1178
|
+
ui.helpBox('Settings', [
|
|
1179
|
+
['theme <name>', 'Change theme (dark/hacker/minimal/ocean/sunset)'],
|
|
1180
|
+
['proxy set <host:port>', 'Set proxy'],
|
|
1181
|
+
['ua <chrome|firefox|mobile>', 'Change user-agent'],
|
|
1182
|
+
['plugin install <name>', 'Install plugin'],
|
|
1183
|
+
]);
|
|
1184
|
+
|
|
1185
|
+
ui.helpBox('General', [
|
|
1186
|
+
['clear', 'Clear screen'],
|
|
1187
|
+
['help', 'Show help'],
|
|
1188
|
+
['help <topic>', 'Topic help (search/browse/tabs/dev)'],
|
|
1189
|
+
['exit', 'Exit browser'],
|
|
1190
|
+
]);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
handleHelpTopic(topic) {
|
|
1194
|
+
switch (topic) {
|
|
1195
|
+
case 'search':
|
|
1196
|
+
ui.helpBox('Search Help', [
|
|
1197
|
+
['search cats', 'Basic search'],
|
|
1198
|
+
['s cats', 'Search shortcut'],
|
|
1199
|
+
['search cats --engine google', 'Use Google'],
|
|
1200
|
+
['search cats --engine duckduckgo', 'Use DuckDuckGo'],
|
|
1201
|
+
['search cats --engine bing', 'Use Bing'],
|
|
1202
|
+
['search cats --type image', 'Image search'],
|
|
1203
|
+
['search cats --type video', 'Video search'],
|
|
1204
|
+
['search cats --type news', 'News search'],
|
|
1205
|
+
['search ai --time day', 'Last 24 hours'],
|
|
1206
|
+
['search ai --time week', 'Last week'],
|
|
1207
|
+
['search ai --multi', 'All categories'],
|
|
1208
|
+
]);
|
|
1209
|
+
break;
|
|
1210
|
+
case 'browse':
|
|
1211
|
+
ui.helpBox('Browse Help', [
|
|
1212
|
+
['open 1', 'Open search result #1'],
|
|
1213
|
+
['open https://example.com', 'Open URL'],
|
|
1214
|
+
['link 3', 'Follow link #3 on page'],
|
|
1215
|
+
['reader', 'Clean reading mode'],
|
|
1216
|
+
['info', 'Page metadata'],
|
|
1217
|
+
['back', 'Go back'],
|
|
1218
|
+
['forward', 'Go forward'],
|
|
1219
|
+
]);
|
|
1220
|
+
break;
|
|
1221
|
+
case 'tabs':
|
|
1222
|
+
ui.helpBox('Tabs Help', [
|
|
1223
|
+
['tab new', 'Create new tab'],
|
|
1224
|
+
['tab switch 2', 'Switch to tab 2'],
|
|
1225
|
+
['tab close 2', 'Close tab 2'],
|
|
1226
|
+
['tabs', 'Show all tabs'],
|
|
1227
|
+
]);
|
|
1228
|
+
break;
|
|
1229
|
+
case 'dev':
|
|
1230
|
+
case 'developer':
|
|
1231
|
+
ui.helpBox('Developer Help', [
|
|
1232
|
+
['inspect', 'Detect frameworks & tech'],
|
|
1233
|
+
['headers', 'HTTP response headers'],
|
|
1234
|
+
['cookies', 'Page cookies'],
|
|
1235
|
+
['scan example.com', 'Full site scan'],
|
|
1236
|
+
['robots', 'Fetch robots.txt'],
|
|
1237
|
+
['sitemap', 'Fetch sitemap.xml'],
|
|
1238
|
+
]);
|
|
1239
|
+
break;
|
|
1240
|
+
default:
|
|
1241
|
+
ui.error(`Unknown help topic: ${topic}`);
|
|
1242
|
+
ui.info('Available: search, browse, tabs, dev');
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// ==================
|
|
1247
|
+
// EXIT
|
|
1248
|
+
// ==================
|
|
1249
|
+
handleExit() {
|
|
1250
|
+
stats.saveTotalTime();
|
|
1251
|
+
console.log();
|
|
1252
|
+
ui.info('Saving session...');
|
|
1253
|
+
const s = stats.getStats();
|
|
1254
|
+
ui.muted?.call(ui, ` Session: ${s.searchCount} searches, ${s.pagesVisited} pages, ${s.sessionTime}`);
|
|
1255
|
+
console.log();
|
|
1256
|
+
ui.success('Goodbye! Thanks for using CLI Browser.');
|
|
1257
|
+
console.log();
|
|
1258
|
+
this.running = false;
|
|
1259
|
+
process.exit(0);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
getAutocompletions(partial) {
|
|
1263
|
+
const commands = [
|
|
1264
|
+
'search', 'open', 'link', 'back', 'forward', 'home', 'refresh',
|
|
1265
|
+
'reader', 'info', 'tab', 'tabs', 'history', 'bookmark',
|
|
1266
|
+
'inspect', 'headers', 'cookies', 'scan', 'robots', 'sitemap',
|
|
1267
|
+
'download', 'downloads', 'summary', 'ask', 'translate',
|
|
1268
|
+
'theme', 'proxy', 'ua', 'plugin', 'session', 'stats',
|
|
1269
|
+
'help', 'clear', 'exit', 'image', 'login', 'color', 'style',
|
|
1270
|
+
];
|
|
1271
|
+
|
|
1272
|
+
if (!partial) return commands;
|
|
1273
|
+
return commands.filter(c => c.startsWith(partial.toLowerCase()));
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
export default Browser;
|