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/src/config.js ADDED
@@ -0,0 +1,51 @@
1
+ import Conf from 'conf';
2
+
3
+ const config = new Conf({
4
+ projectName: 'cli-browser',
5
+ defaults: {
6
+ searxng: 'https://searxng.exe.xyz',
7
+ theme: 'dark',
8
+ colors: {
9
+ links: 'cyan',
10
+ title: 'green',
11
+ url: 'gray',
12
+ highlight: 'yellow',
13
+ error: 'red',
14
+ success: 'green',
15
+ info: 'blue',
16
+ muted: 'gray',
17
+ border: 'white',
18
+ },
19
+ style: 'default',
20
+ userAgent: 'CLI-Browser/1.0',
21
+ proxy: null,
22
+ downloadDir: './downloads',
23
+ maxHistory: 500,
24
+ maxTabs: 20,
25
+ pageSize: 10,
26
+ bangCommands: {
27
+ '!yt': 'https://www.youtube.com/results?search_query=',
28
+ '!gh': 'https://github.com/search?q=',
29
+ '!wiki': 'https://en.wikipedia.org/wiki/Special:Search?search=',
30
+ '!npm': 'https://www.npmjs.com/search?q=',
31
+ '!so': 'https://stackoverflow.com/search?q=',
32
+ '!reddit': 'https://www.reddit.com/search/?q=',
33
+ '!mdn': 'https://developer.mozilla.org/en-US/search?q=',
34
+ '!twitter': 'https://twitter.com/search?q=',
35
+ '!amazon': 'https://www.amazon.com/s?k=',
36
+ '!maps': 'https://www.google.com/maps/search/',
37
+ },
38
+ bookmarks: [],
39
+ history: [],
40
+ stats: {
41
+ searchCount: 0,
42
+ pagesVisited: 0,
43
+ startTime: Date.now(),
44
+ totalTime: 0,
45
+ },
46
+ plugins: [],
47
+ sessions: {},
48
+ },
49
+ });
50
+
51
+ export default config;
@@ -0,0 +1,246 @@
1
+ import axios from 'axios';
2
+ import * as cheerio from 'cheerio';
3
+ import { theme } from './themes.js';
4
+ import config from './config.js';
5
+
6
+ class Developer {
7
+ inspect(html, url) {
8
+ const $ = cheerio.load(html);
9
+ const result = {
10
+ framework: this._detectFramework($),
11
+ scripts: this._getScripts($, url),
12
+ apis: this._detectAPIs($),
13
+ metaTags: this._getMetaTags($),
14
+ technologies: this._detectTechnologies($, html),
15
+ };
16
+ return result;
17
+ }
18
+
19
+ _detectFramework($) {
20
+ const frameworks = [];
21
+
22
+ if ($('[ng-app], [data-ng-app], [ng-controller]').length || $('script[src*="angular"]').length) {
23
+ frameworks.push('Angular');
24
+ }
25
+ if ($('[data-reactroot], [data-reactid]').length || $('script[src*="react"]').length || $('#__next').length) {
26
+ frameworks.push('React');
27
+ }
28
+ if ($('[data-v-], [v-cloak]').length || $('script[src*="vue"]').length || $('#__nuxt').length) {
29
+ frameworks.push('Vue.js');
30
+ }
31
+ if ($('script[src*="jquery"]').length || $('script[src*="jQuery"]').length) {
32
+ frameworks.push('jQuery');
33
+ }
34
+ if ($('script[src*="bootstrap"]').length || $('link[href*="bootstrap"]').length) {
35
+ frameworks.push('Bootstrap');
36
+ }
37
+ if ($('script[src*="tailwind"]').length || $('link[href*="tailwind"]').length) {
38
+ frameworks.push('Tailwind CSS');
39
+ }
40
+ if ($('meta[name="generator"][content*="WordPress"]').length) {
41
+ frameworks.push('WordPress');
42
+ }
43
+ if ($('meta[name="generator"][content*="Next.js"]').length || $('#__next').length) {
44
+ frameworks.push('Next.js');
45
+ }
46
+ if ($('#__nuxt').length || $('[data-n-head]').length) {
47
+ frameworks.push('Nuxt.js');
48
+ }
49
+ if ($('meta[name="generator"][content*="Gatsby"]').length || $('#___gatsby').length) {
50
+ frameworks.push('Gatsby');
51
+ }
52
+ if ($('script[src*="svelte"]').length) {
53
+ frameworks.push('Svelte');
54
+ }
55
+
56
+ return frameworks.length ? frameworks : ['Unknown'];
57
+ }
58
+
59
+ _getScripts($, url) {
60
+ const scripts = [];
61
+ $('script[src]').each((_, el) => {
62
+ let src = $(el).attr('src');
63
+ if (src) {
64
+ if (src.startsWith('/') && url) {
65
+ try {
66
+ const base = new URL(url);
67
+ src = `${base.protocol}//${base.host}${src}`;
68
+ } catch {}
69
+ }
70
+ scripts.push(src);
71
+ }
72
+ });
73
+ return scripts.slice(0, 20);
74
+ }
75
+
76
+ _detectAPIs($) {
77
+ const apis = [];
78
+ $('script').each((_, el) => {
79
+ const text = $(el).html() || '';
80
+ const matches = text.match(/(?:fetch|axios|XMLHttpRequest|\.ajax)\s*\(\s*['"`]([^'"`]+)['"`]/g);
81
+ if (matches) {
82
+ apis.push(...matches.slice(0, 5));
83
+ }
84
+ });
85
+ return apis.slice(0, 10);
86
+ }
87
+
88
+ _getMetaTags($) {
89
+ const meta = {};
90
+ $('meta').each((_, el) => {
91
+ const name = $(el).attr('name') || $(el).attr('property') || $(el).attr('http-equiv');
92
+ const content = $(el).attr('content');
93
+ if (name && content) {
94
+ meta[name] = content;
95
+ }
96
+ });
97
+ return meta;
98
+ }
99
+
100
+ _detectTechnologies($, html) {
101
+ const techs = [];
102
+
103
+ if (html.includes('Google Analytics') || html.includes('gtag') || html.includes('UA-')) {
104
+ techs.push('Google Analytics');
105
+ }
106
+ if (html.includes('Google Tag Manager') || html.includes('gtm.js')) {
107
+ techs.push('Google Tag Manager');
108
+ }
109
+ if (html.includes('fb-root') || html.includes('facebook')) {
110
+ techs.push('Facebook SDK');
111
+ }
112
+ if (html.includes('cloudflare')) {
113
+ techs.push('Cloudflare');
114
+ }
115
+ if ($('link[href*="fonts.googleapis"]').length) {
116
+ techs.push('Google Fonts');
117
+ }
118
+ if ($('link[href*="fontawesome"]').length || $('script[src*="fontawesome"]').length) {
119
+ techs.push('Font Awesome');
120
+ }
121
+
122
+ return techs;
123
+ }
124
+
125
+ async getHeaders(url) {
126
+ const response = await axios.head(url, {
127
+ headers: { 'User-Agent': config.get('userAgent') },
128
+ timeout: 10000,
129
+ validateStatus: () => true,
130
+ });
131
+ return {
132
+ status: response.status,
133
+ statusText: response.statusText,
134
+ headers: response.headers,
135
+ };
136
+ }
137
+
138
+ getCookies(html, headers) {
139
+ const cookies = [];
140
+ if (headers && headers['set-cookie']) {
141
+ const setCookies = Array.isArray(headers['set-cookie'])
142
+ ? headers['set-cookie']
143
+ : [headers['set-cookie']];
144
+ setCookies.forEach(c => {
145
+ const parts = c.split(';')[0].split('=');
146
+ cookies.push({
147
+ name: parts[0]?.trim(),
148
+ value: parts.slice(1).join('=')?.trim(),
149
+ raw: c,
150
+ });
151
+ });
152
+ }
153
+ return cookies;
154
+ }
155
+
156
+ async scan(url) {
157
+ const result = {
158
+ url,
159
+ headers: null,
160
+ technologies: [],
161
+ openPorts: [],
162
+ links: [],
163
+ robots: null,
164
+ };
165
+
166
+ try {
167
+ const headerInfo = await this.getHeaders(url);
168
+ result.headers = headerInfo;
169
+
170
+ // Check security headers
171
+ const secHeaders = headerInfo.headers;
172
+ result.security = {
173
+ https: url.startsWith('https'),
174
+ hsts: !!secHeaders['strict-transport-security'],
175
+ csp: !!secHeaders['content-security-policy'],
176
+ xframe: secHeaders['x-frame-options'] || 'Not set',
177
+ xcontent: secHeaders['x-content-type-options'] || 'Not set',
178
+ xss: secHeaders['x-xss-protection'] || 'Not set',
179
+ server: secHeaders['server'] || 'Hidden',
180
+ poweredBy: secHeaders['x-powered-by'] || 'Hidden',
181
+ };
182
+
183
+ // Fetch page for tech detection
184
+ const pageRes = await axios.get(url, {
185
+ headers: { 'User-Agent': config.get('userAgent') },
186
+ timeout: 10000,
187
+ });
188
+
189
+ const $ = cheerio.load(pageRes.data);
190
+ result.technologies = this._detectTechnologies($, pageRes.data);
191
+ result.frameworks = this._detectFramework($);
192
+
193
+ // Get all links
194
+ const links = new Set();
195
+ $('a[href]').each((_, el) => {
196
+ const href = $(el).attr('href');
197
+ if (href && !href.startsWith('#') && !href.startsWith('javascript:')) {
198
+ links.add(href);
199
+ }
200
+ });
201
+ result.links = [...links].slice(0, 30);
202
+
203
+ } catch (err) {
204
+ result.error = err.message;
205
+ }
206
+
207
+ return result;
208
+ }
209
+
210
+ async getRobots(url) {
211
+ try {
212
+ const base = new URL(url);
213
+ const robotsUrl = `${base.protocol}//${base.host}/robots.txt`;
214
+ const res = await axios.get(robotsUrl, {
215
+ headers: { 'User-Agent': config.get('userAgent') },
216
+ timeout: 10000,
217
+ });
218
+ return res.data;
219
+ } catch {
220
+ return 'robots.txt not found';
221
+ }
222
+ }
223
+
224
+ async getSitemap(url) {
225
+ try {
226
+ const base = new URL(url);
227
+ const sitemapUrl = `${base.protocol}//${base.host}/sitemap.xml`;
228
+ const res = await axios.get(sitemapUrl, {
229
+ headers: { 'User-Agent': config.get('userAgent') },
230
+ timeout: 10000,
231
+ });
232
+
233
+ const $ = cheerio.load(res.data, { xmlMode: true });
234
+ const urls = [];
235
+ $('url loc').each((_, el) => {
236
+ urls.push($(el).text());
237
+ });
238
+
239
+ return urls.length ? urls : res.data.slice(0, 2000);
240
+ } catch {
241
+ return 'sitemap.xml not found';
242
+ }
243
+ }
244
+ }
245
+
246
+ export default new Developer();
@@ -0,0 +1,80 @@
1
+ import axios from 'axios';
2
+ import { createWriteStream, existsSync, mkdirSync } from 'fs';
3
+ import { basename, join } from 'path';
4
+ import config from './config.js';
5
+
6
+ class DownloadManager {
7
+ constructor() {
8
+ this.downloads = [];
9
+ this.downloadDir = config.get('downloadDir');
10
+ }
11
+
12
+ async download(url, filename) {
13
+ const dir = this.downloadDir;
14
+ if (!existsSync(dir)) {
15
+ mkdirSync(dir, { recursive: true });
16
+ }
17
+
18
+ const fname = filename || basename(new URL(url).pathname) || `download_${Date.now()}`;
19
+ const filepath = join(dir, fname);
20
+
21
+ const entry = {
22
+ id: this.downloads.length + 1,
23
+ url,
24
+ filename: fname,
25
+ filepath,
26
+ status: 'downloading',
27
+ startTime: Date.now(),
28
+ size: 0,
29
+ downloaded: 0,
30
+ };
31
+
32
+ this.downloads.push(entry);
33
+
34
+ try {
35
+ const response = await axios({
36
+ method: 'GET',
37
+ url,
38
+ responseType: 'stream',
39
+ headers: { 'User-Agent': config.get('userAgent') },
40
+ timeout: 60000,
41
+ });
42
+
43
+ entry.size = parseInt(response.headers['content-length'] || 0);
44
+
45
+ const writer = createWriteStream(filepath);
46
+
47
+ response.data.on('data', (chunk) => {
48
+ entry.downloaded += chunk.length;
49
+ });
50
+
51
+ await new Promise((resolve, reject) => {
52
+ response.data.pipe(writer);
53
+ writer.on('finish', resolve);
54
+ writer.on('error', reject);
55
+ });
56
+
57
+ entry.status = 'complete';
58
+ entry.endTime = Date.now();
59
+ return entry;
60
+ } catch (err) {
61
+ entry.status = 'failed';
62
+ entry.error = err.message;
63
+ throw err;
64
+ }
65
+ }
66
+
67
+ getDownloads() {
68
+ return this.downloads;
69
+ }
70
+
71
+ formatSize(bytes) {
72
+ if (bytes === 0) return '0 B';
73
+ const k = 1024;
74
+ const sizes = ['B', 'KB', 'MB', 'GB'];
75
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
76
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
77
+ }
78
+ }
79
+
80
+ export default new DownloadManager();
package/src/history.js ADDED
@@ -0,0 +1,58 @@
1
+ import config from './config.js';
2
+
3
+ class HistoryManager {
4
+ constructor() {
5
+ this.sessionHistory = [];
6
+ }
7
+
8
+ add(entry) {
9
+ const item = {
10
+ id: Date.now(),
11
+ timestamp: new Date().toISOString(),
12
+ type: entry.type || 'page', // 'search' | 'page'
13
+ title: entry.title || '',
14
+ url: entry.url || '',
15
+ query: entry.query || '',
16
+ };
17
+
18
+ this.sessionHistory.push(item);
19
+
20
+ // Also persist
21
+ const saved = config.get('history') || [];
22
+ saved.push(item);
23
+
24
+ // Trim to max
25
+ const max = config.get('maxHistory');
26
+ if (saved.length > max) {
27
+ saved.splice(0, saved.length - max);
28
+ }
29
+ config.set('history', saved);
30
+
31
+ return item;
32
+ }
33
+
34
+ getAll() {
35
+ return config.get('history') || [];
36
+ }
37
+
38
+ getSession() {
39
+ return this.sessionHistory;
40
+ }
41
+
42
+ getByIndex(idx) {
43
+ const all = this.getAll();
44
+ return all[idx - 1] || null;
45
+ }
46
+
47
+ clear() {
48
+ this.sessionHistory = [];
49
+ config.set('history', []);
50
+ }
51
+
52
+ getRecent(count = 20) {
53
+ const all = this.getAll();
54
+ return all.slice(-count).reverse();
55
+ }
56
+ }
57
+
58
+ export default new HistoryManager();
package/src/index.js ADDED
@@ -0,0 +1,45 @@
1
+ import { createInterface } from 'readline';
2
+ import Browser from './browser.js';
3
+ import { theme } from './themes.js';
4
+ import * as ui from './ui.js';
5
+
6
+ export async function startBrowser() {
7
+ const browser = new Browser();
8
+
9
+ // Show banner
10
+ console.clear();
11
+ ui.banner();
12
+ ui.info('Type "help" for commands, or start searching!');
13
+ ui.info('Type "search <query>" or just type anything to search.');
14
+ console.log();
15
+
16
+ const rl = createInterface({
17
+ input: process.stdin,
18
+ output: process.stdout,
19
+ prompt: ui.prompt(),
20
+ completer: (line) => {
21
+ const completions = browser.getAutocompletions(line);
22
+ return [completions, line];
23
+ },
24
+ terminal: true,
25
+ });
26
+
27
+ rl.prompt();
28
+
29
+ rl.on('line', async (line) => {
30
+ const input = line.trim();
31
+ if (input) {
32
+ await browser.handleCommand(input);
33
+ }
34
+ rl.prompt();
35
+ });
36
+
37
+ rl.on('close', () => {
38
+ browser.handleExit();
39
+ });
40
+
41
+ // Handle SIGINT gracefully
42
+ process.on('SIGINT', () => {
43
+ browser.handleExit();
44
+ });
45
+ }
package/src/network.js ADDED
@@ -0,0 +1,56 @@
1
+ import config from './config.js';
2
+
3
+ class NetworkManager {
4
+ setProxy(proxy) {
5
+ config.set('proxy', proxy);
6
+ return proxy;
7
+ }
8
+
9
+ clearProxy() {
10
+ config.set('proxy', null);
11
+ }
12
+
13
+ getProxy() {
14
+ return config.get('proxy');
15
+ }
16
+
17
+ setUserAgent(preset) {
18
+ const agents = {
19
+ chrome: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
20
+ firefox: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
21
+ safari: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
22
+ mobile: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
23
+ edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
24
+ bot: 'CLI-Browser/1.0 (Terminal Internet)',
25
+ curl: 'curl/8.0',
26
+ default: 'CLI-Browser/1.0',
27
+ };
28
+
29
+ const ua = agents[preset] || preset;
30
+ config.set('userAgent', ua);
31
+ return ua;
32
+ }
33
+
34
+ getUserAgent() {
35
+ return config.get('userAgent');
36
+ }
37
+
38
+ getAxiosConfig() {
39
+ const axiosConfig = {
40
+ headers: {
41
+ 'User-Agent': config.get('userAgent'),
42
+ },
43
+ timeout: 15000,
44
+ };
45
+
46
+ const proxy = config.get('proxy');
47
+ if (proxy) {
48
+ const [host, port] = proxy.split(':');
49
+ axiosConfig.proxy = { host, port: parseInt(port) };
50
+ }
51
+
52
+ return axiosConfig;
53
+ }
54
+ }
55
+
56
+ export default new NetworkManager();
package/src/plugins.js ADDED
@@ -0,0 +1,74 @@
1
+ import config from './config.js';
2
+
3
+ class PluginManager {
4
+ constructor() {
5
+ this.loadedPlugins = new Map();
6
+ }
7
+
8
+ listInstalled() {
9
+ return config.get('plugins') || [];
10
+ }
11
+
12
+ async install(name) {
13
+ const plugins = config.get('plugins') || [];
14
+ if (plugins.find(p => p.name === name)) {
15
+ throw new Error(`Plugin "${name}" is already installed`);
16
+ }
17
+
18
+ // Built-in plugin definitions
19
+ const builtIn = {
20
+ youtube: {
21
+ name: 'youtube',
22
+ description: 'Quick YouTube search and video info',
23
+ bang: '!yt',
24
+ version: '1.0.0',
25
+ },
26
+ wikipedia: {
27
+ name: 'wikipedia',
28
+ description: 'Quick Wikipedia article lookup',
29
+ bang: '!wiki',
30
+ version: '1.0.0',
31
+ },
32
+ github: {
33
+ name: 'github',
34
+ description: 'Quick GitHub repository search',
35
+ bang: '!gh',
36
+ version: '1.0.0',
37
+ },
38
+ stackoverflow: {
39
+ name: 'stackoverflow',
40
+ description: 'Quick Stack Overflow search',
41
+ bang: '!so',
42
+ version: '1.0.0',
43
+ },
44
+ reddit: {
45
+ name: 'reddit',
46
+ description: 'Quick Reddit search',
47
+ bang: '!reddit',
48
+ version: '1.0.0',
49
+ },
50
+ };
51
+
52
+ const plugin = builtIn[name];
53
+ if (!plugin) {
54
+ throw new Error(`Plugin "${name}" not found. Available: ${Object.keys(builtIn).join(', ')}`);
55
+ }
56
+
57
+ plugins.push(plugin);
58
+ config.set('plugins', plugins);
59
+ return plugin;
60
+ }
61
+
62
+ uninstall(name) {
63
+ const plugins = config.get('plugins') || [];
64
+ const idx = plugins.findIndex(p => p.name === name);
65
+ if (idx === -1) {
66
+ throw new Error(`Plugin "${name}" is not installed`);
67
+ }
68
+ const removed = plugins.splice(idx, 1);
69
+ config.set('plugins', plugins);
70
+ return removed[0];
71
+ }
72
+ }
73
+
74
+ export default new PluginManager();