recker 1.0.26 → 1.0.27-next.1cb21f8

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.
Files changed (213) hide show
  1. package/dist/browser/browser/cache.d.ts +40 -0
  2. package/dist/browser/browser/cache.js +199 -0
  3. package/dist/browser/browser/crypto.d.ts +24 -0
  4. package/dist/browser/browser/crypto.js +80 -0
  5. package/dist/browser/browser/index.d.ts +31 -0
  6. package/dist/browser/browser/index.js +31 -0
  7. package/dist/browser/browser/recker.d.ts +26 -0
  8. package/dist/browser/browser/recker.js +61 -0
  9. package/dist/browser/cache/basic-file-storage.d.ts +12 -0
  10. package/dist/browser/cache/basic-file-storage.js +50 -0
  11. package/dist/browser/cache/memory-limits.d.ts +20 -0
  12. package/dist/browser/cache/memory-limits.js +96 -0
  13. package/dist/browser/cache/memory-storage.d.ts +132 -0
  14. package/dist/browser/cache/memory-storage.js +454 -0
  15. package/dist/browser/cache.d.ts +40 -0
  16. package/dist/browser/cache.js +199 -0
  17. package/dist/browser/constants/http-status.d.ts +73 -0
  18. package/dist/browser/constants/http-status.js +156 -0
  19. package/dist/browser/cookies/memory-cookie-jar.d.ts +30 -0
  20. package/dist/browser/cookies/memory-cookie-jar.js +210 -0
  21. package/dist/browser/core/client.d.ts +118 -0
  22. package/dist/browser/core/client.js +667 -0
  23. package/dist/browser/core/errors.d.ts +142 -0
  24. package/dist/browser/core/errors.js +308 -0
  25. package/dist/browser/core/index.d.ts +5 -0
  26. package/dist/browser/core/index.js +5 -0
  27. package/dist/browser/core/request-promise.d.ts +23 -0
  28. package/dist/browser/core/request-promise.js +82 -0
  29. package/dist/browser/core/request.d.ts +20 -0
  30. package/dist/browser/core/request.js +76 -0
  31. package/dist/browser/core/response.d.ts +34 -0
  32. package/dist/browser/core/response.js +178 -0
  33. package/dist/browser/crypto.d.ts +24 -0
  34. package/dist/browser/crypto.js +80 -0
  35. package/dist/browser/index.d.ts +31 -0
  36. package/dist/browser/index.js +31 -0
  37. package/dist/browser/plugins/auth/api-key.d.ts +8 -0
  38. package/dist/browser/plugins/auth/api-key.js +27 -0
  39. package/dist/browser/plugins/auth/auth0.d.ts +33 -0
  40. package/dist/browser/plugins/auth/auth0.js +94 -0
  41. package/dist/browser/plugins/auth/aws-sigv4.d.ts +10 -0
  42. package/dist/browser/plugins/auth/aws-sigv4.js +88 -0
  43. package/dist/browser/plugins/auth/azure-ad.d.ts +48 -0
  44. package/dist/browser/plugins/auth/azure-ad.js +152 -0
  45. package/dist/browser/plugins/auth/basic.d.ts +7 -0
  46. package/dist/browser/plugins/auth/basic.js +13 -0
  47. package/dist/browser/plugins/auth/bearer.d.ts +8 -0
  48. package/dist/browser/plugins/auth/bearer.js +17 -0
  49. package/dist/browser/plugins/auth/cognito.d.ts +45 -0
  50. package/dist/browser/plugins/auth/cognito.js +208 -0
  51. package/dist/browser/plugins/auth/digest.d.ts +8 -0
  52. package/dist/browser/plugins/auth/digest.js +100 -0
  53. package/dist/browser/plugins/auth/firebase.d.ts +32 -0
  54. package/dist/browser/plugins/auth/firebase.js +195 -0
  55. package/dist/browser/plugins/auth/github-app.d.ts +36 -0
  56. package/dist/browser/plugins/auth/github-app.js +170 -0
  57. package/dist/browser/plugins/auth/google-service-account.d.ts +49 -0
  58. package/dist/browser/plugins/auth/google-service-account.js +172 -0
  59. package/dist/browser/plugins/auth/index.d.ts +15 -0
  60. package/dist/browser/plugins/auth/index.js +15 -0
  61. package/dist/browser/plugins/auth/mtls.d.ts +37 -0
  62. package/dist/browser/plugins/auth/mtls.js +140 -0
  63. package/dist/browser/plugins/auth/oauth2.d.ts +8 -0
  64. package/dist/browser/plugins/auth/oauth2.js +26 -0
  65. package/dist/browser/plugins/auth/oidc.d.ts +55 -0
  66. package/dist/browser/plugins/auth/oidc.js +222 -0
  67. package/dist/browser/plugins/auth/okta.d.ts +47 -0
  68. package/dist/browser/plugins/auth/okta.js +157 -0
  69. package/dist/browser/plugins/auth.d.ts +1 -0
  70. package/dist/browser/plugins/auth.js +1 -0
  71. package/dist/browser/plugins/cache.d.ts +15 -0
  72. package/dist/browser/plugins/cache.js +486 -0
  73. package/dist/browser/plugins/circuit-breaker.d.ts +13 -0
  74. package/dist/browser/plugins/circuit-breaker.js +100 -0
  75. package/dist/browser/plugins/compression.d.ts +4 -0
  76. package/dist/browser/plugins/compression.js +130 -0
  77. package/dist/browser/plugins/cookie-jar.d.ts +5 -0
  78. package/dist/browser/plugins/cookie-jar.js +72 -0
  79. package/dist/browser/plugins/dedup.d.ts +5 -0
  80. package/dist/browser/plugins/dedup.js +35 -0
  81. package/dist/browser/plugins/graphql.d.ts +13 -0
  82. package/dist/browser/plugins/graphql.js +58 -0
  83. package/dist/browser/plugins/grpc-web.d.ts +79 -0
  84. package/dist/browser/plugins/grpc-web.js +261 -0
  85. package/dist/browser/plugins/hls.d.ts +105 -0
  86. package/dist/browser/plugins/hls.js +395 -0
  87. package/dist/browser/plugins/jsonrpc.d.ts +75 -0
  88. package/dist/browser/plugins/jsonrpc.js +143 -0
  89. package/dist/browser/plugins/logger.d.ts +13 -0
  90. package/dist/browser/plugins/logger.js +108 -0
  91. package/dist/browser/plugins/odata.d.ts +181 -0
  92. package/dist/browser/plugins/odata.js +564 -0
  93. package/dist/browser/plugins/pagination.d.ts +16 -0
  94. package/dist/browser/plugins/pagination.js +105 -0
  95. package/dist/browser/plugins/rate-limit.d.ts +15 -0
  96. package/dist/browser/plugins/rate-limit.js +162 -0
  97. package/dist/browser/plugins/retry.d.ts +14 -0
  98. package/dist/browser/plugins/retry.js +116 -0
  99. package/dist/browser/plugins/scrape.d.ts +21 -0
  100. package/dist/browser/plugins/scrape.js +82 -0
  101. package/dist/browser/plugins/server-timing.d.ts +7 -0
  102. package/dist/browser/plugins/server-timing.js +24 -0
  103. package/dist/browser/plugins/soap.d.ts +72 -0
  104. package/dist/browser/plugins/soap.js +347 -0
  105. package/dist/browser/plugins/xml.d.ts +9 -0
  106. package/dist/browser/plugins/xml.js +194 -0
  107. package/dist/browser/plugins/xsrf.d.ts +9 -0
  108. package/dist/browser/plugins/xsrf.js +48 -0
  109. package/dist/browser/recker.d.ts +26 -0
  110. package/dist/browser/recker.js +61 -0
  111. package/dist/browser/runner/request-runner.d.ts +46 -0
  112. package/dist/browser/runner/request-runner.js +89 -0
  113. package/dist/browser/scrape/document.d.ts +44 -0
  114. package/dist/browser/scrape/document.js +210 -0
  115. package/dist/browser/scrape/element.d.ts +49 -0
  116. package/dist/browser/scrape/element.js +176 -0
  117. package/dist/browser/scrape/extractors.d.ts +16 -0
  118. package/dist/browser/scrape/extractors.js +357 -0
  119. package/dist/browser/scrape/types.d.ts +108 -0
  120. package/dist/browser/scrape/types.js +1 -0
  121. package/dist/browser/transport/fetch.d.ts +11 -0
  122. package/dist/browser/transport/fetch.js +143 -0
  123. package/dist/browser/transport/undici.d.ts +38 -0
  124. package/dist/browser/transport/undici.js +897 -0
  125. package/dist/browser/types/ai.d.ts +267 -0
  126. package/dist/browser/types/ai.js +1 -0
  127. package/dist/browser/types/index.d.ts +351 -0
  128. package/dist/browser/types/index.js +1 -0
  129. package/dist/browser/types/logger.d.ts +16 -0
  130. package/dist/browser/types/logger.js +66 -0
  131. package/dist/browser/types/udp.d.ts +138 -0
  132. package/dist/browser/types/udp.js +1 -0
  133. package/dist/browser/utils/agent-manager.d.ts +29 -0
  134. package/dist/browser/utils/agent-manager.js +160 -0
  135. package/dist/browser/utils/body.d.ts +10 -0
  136. package/dist/browser/utils/body.js +148 -0
  137. package/dist/browser/utils/charset.d.ts +15 -0
  138. package/dist/browser/utils/charset.js +169 -0
  139. package/dist/browser/utils/concurrency.d.ts +20 -0
  140. package/dist/browser/utils/concurrency.js +120 -0
  141. package/dist/browser/utils/dns.d.ts +6 -0
  142. package/dist/browser/utils/dns.js +26 -0
  143. package/dist/browser/utils/header-parser.d.ts +94 -0
  144. package/dist/browser/utils/header-parser.js +617 -0
  145. package/dist/browser/utils/html-cleaner.d.ts +1 -0
  146. package/dist/browser/utils/html-cleaner.js +21 -0
  147. package/dist/browser/utils/link-header.d.ts +69 -0
  148. package/dist/browser/utils/link-header.js +190 -0
  149. package/dist/browser/utils/optional-require.d.ts +19 -0
  150. package/dist/browser/utils/optional-require.js +105 -0
  151. package/dist/browser/utils/progress.d.ts +8 -0
  152. package/dist/browser/utils/progress.js +82 -0
  153. package/dist/browser/utils/request-pool.d.ts +22 -0
  154. package/dist/browser/utils/request-pool.js +101 -0
  155. package/dist/browser/utils/sse.d.ts +7 -0
  156. package/dist/browser/utils/sse.js +67 -0
  157. package/dist/browser/utils/streaming.d.ts +17 -0
  158. package/dist/browser/utils/streaming.js +84 -0
  159. package/dist/browser/utils/try-fn.d.ts +3 -0
  160. package/dist/browser/utils/try-fn.js +59 -0
  161. package/dist/browser/utils/user-agent.d.ts +44 -0
  162. package/dist/browser/utils/user-agent.js +100 -0
  163. package/dist/browser/utils/whois.d.ts +32 -0
  164. package/dist/browser/utils/whois.js +246 -0
  165. package/dist/browser/websocket/client.d.ts +65 -0
  166. package/dist/browser/websocket/client.js +313 -0
  167. package/dist/cli/index.d.ts +1 -0
  168. package/dist/cli/index.js +143 -3
  169. package/dist/cli/tui/shell.d.ts +1 -0
  170. package/dist/cli/tui/shell.js +157 -0
  171. package/dist/index.d.ts +1 -0
  172. package/dist/index.js +1 -0
  173. package/dist/scrape/extractors.js +2 -1
  174. package/dist/scrape/types.d.ts +2 -1
  175. package/dist/seo/analyzer.d.ts +42 -0
  176. package/dist/seo/analyzer.js +715 -0
  177. package/dist/seo/index.d.ts +5 -0
  178. package/dist/seo/index.js +2 -0
  179. package/dist/seo/rules/accessibility.d.ts +2 -0
  180. package/dist/seo/rules/accessibility.js +128 -0
  181. package/dist/seo/rules/content.d.ts +2 -0
  182. package/dist/seo/rules/content.js +236 -0
  183. package/dist/seo/rules/images.d.ts +2 -0
  184. package/dist/seo/rules/images.js +180 -0
  185. package/dist/seo/rules/index.d.ts +20 -0
  186. package/dist/seo/rules/index.js +72 -0
  187. package/dist/seo/rules/links.d.ts +2 -0
  188. package/dist/seo/rules/links.js +150 -0
  189. package/dist/seo/rules/meta.d.ts +2 -0
  190. package/dist/seo/rules/meta.js +523 -0
  191. package/dist/seo/rules/mobile.d.ts +2 -0
  192. package/dist/seo/rules/mobile.js +71 -0
  193. package/dist/seo/rules/performance.d.ts +2 -0
  194. package/dist/seo/rules/performance.js +246 -0
  195. package/dist/seo/rules/schema.d.ts +2 -0
  196. package/dist/seo/rules/schema.js +54 -0
  197. package/dist/seo/rules/security.d.ts +2 -0
  198. package/dist/seo/rules/security.js +147 -0
  199. package/dist/seo/rules/structural.d.ts +2 -0
  200. package/dist/seo/rules/structural.js +155 -0
  201. package/dist/seo/rules/technical.d.ts +2 -0
  202. package/dist/seo/rules/technical.js +223 -0
  203. package/dist/seo/rules/thresholds.d.ts +196 -0
  204. package/dist/seo/rules/thresholds.js +118 -0
  205. package/dist/seo/rules/types.d.ts +191 -0
  206. package/dist/seo/rules/types.js +11 -0
  207. package/dist/seo/types.d.ts +160 -0
  208. package/dist/seo/types.js +1 -0
  209. package/dist/transport/fetch.d.ts +7 -1
  210. package/dist/transport/fetch.js +58 -76
  211. package/dist/utils/columns.d.ts +14 -0
  212. package/dist/utils/columns.js +69 -0
  213. package/package.json +34 -2
@@ -0,0 +1,313 @@
1
+ import { WebSocket } from 'undici';
2
+ import { EventEmitter } from 'events';
3
+ import { pipeline } from 'node:stream/promises';
4
+ import { webToNodeStream } from '../utils/streaming.js';
5
+ import { StateError, StreamError, ConnectionError } from '../core/errors.js';
6
+ export class ReckerWebSocket extends EventEmitter {
7
+ ws = null;
8
+ url;
9
+ options;
10
+ reconnectAttempts = 0;
11
+ reconnectTimer;
12
+ heartbeatTimer;
13
+ isClosed = false;
14
+ isReconnecting = false;
15
+ pongWatchdog;
16
+ backoff;
17
+ closedByUser = false;
18
+ constructor(url, options = {}) {
19
+ super();
20
+ this.url = url;
21
+ this.options = {
22
+ protocols: options.protocols || [],
23
+ headers: options.headers || {},
24
+ reconnect: options.reconnect ?? false,
25
+ reconnectDelay: options.reconnectDelay ?? 1000,
26
+ maxReconnectAttempts: options.maxReconnectAttempts ?? 5,
27
+ heartbeatInterval: options.heartbeatInterval ?? 30000,
28
+ heartbeatTimeout: options.heartbeatTimeout ?? 10000,
29
+ dispatcher: options.dispatcher,
30
+ proxy: options.proxy,
31
+ tls: options.tls,
32
+ perMessageDeflate: options.perMessageDeflate ?? false
33
+ };
34
+ this.backoff = {
35
+ base: this.options.reconnectDelay,
36
+ factor: 2,
37
+ jitter: true,
38
+ max: 30000
39
+ };
40
+ }
41
+ async connect() {
42
+ return new Promise((resolve, reject) => {
43
+ try {
44
+ const wsOptions = {
45
+ headers: this.options.headers,
46
+ dispatcher: this.options.dispatcher,
47
+ perMessageDeflate: this.options.perMessageDeflate,
48
+ };
49
+ if (this.options.proxy) {
50
+ const proxyConfig = typeof this.options.proxy === 'string'
51
+ ? { url: this.options.proxy }
52
+ : this.options.proxy;
53
+ const { ProxyAgent } = require('undici');
54
+ wsOptions.dispatcher = new ProxyAgent(proxyConfig.url);
55
+ }
56
+ if (this.options.tls) {
57
+ wsOptions.tls = this.options.tls;
58
+ }
59
+ this.ws = new WebSocket(this.url, this.options.protocols, wsOptions);
60
+ this.ws.addEventListener('open', () => {
61
+ this.reconnectAttempts = 0;
62
+ this.isReconnecting = false;
63
+ this.startHeartbeat();
64
+ this.emit('open');
65
+ resolve();
66
+ });
67
+ this.ws.addEventListener('message', (event) => {
68
+ const message = {
69
+ data: event.data,
70
+ isBinary: event.data instanceof Buffer
71
+ };
72
+ this.emit('message', message);
73
+ this.stopPongWatchdog();
74
+ });
75
+ this.ws.addEventListener('close', (event) => {
76
+ this.stopHeartbeat();
77
+ this.stopPongWatchdog();
78
+ this.emit('close', event.code, event.reason);
79
+ if (!this.closedByUser && !this.isClosed && this.options.reconnect) {
80
+ this.attemptReconnect();
81
+ }
82
+ });
83
+ this.ws.addEventListener('error', (event) => {
84
+ const err = event.error instanceof Error
85
+ ? event.error
86
+ : new ConnectionError('WebSocket connection error', {
87
+ host: this.url,
88
+ retriable: true,
89
+ });
90
+ this.emit('error', err);
91
+ reject(err);
92
+ });
93
+ }
94
+ catch (error) {
95
+ reject(error);
96
+ }
97
+ });
98
+ }
99
+ async send(data, options) {
100
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
101
+ throw new StateError('WebSocket is not connected', {
102
+ expectedState: 'open',
103
+ actualState: this.ws ? 'closed' : 'not-created',
104
+ });
105
+ }
106
+ const awaitDrain = options?.awaitDrain ?? false;
107
+ const highWaterMark = options?.highWaterMark ?? 16 * 1024;
108
+ this.ws.send(data);
109
+ if (awaitDrain) {
110
+ await this.waitForDrain(highWaterMark);
111
+ }
112
+ }
113
+ async sendStream(stream, options) {
114
+ for await (const chunk of stream) {
115
+ await this.send(chunk, options);
116
+ }
117
+ }
118
+ sendJSON(data) {
119
+ void this.send(JSON.stringify(data));
120
+ }
121
+ close(code = 1000, reason = '') {
122
+ this.isClosed = true;
123
+ this.closedByUser = true;
124
+ this.stopHeartbeat();
125
+ this.clearReconnectTimer();
126
+ if (this.ws) {
127
+ this.ws.close(code, reason);
128
+ this.ws = null;
129
+ }
130
+ }
131
+ ping() {
132
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
133
+ return;
134
+ const anyWs = this.ws;
135
+ if (typeof anyWs.ping === 'function') {
136
+ try {
137
+ anyWs.ping();
138
+ return;
139
+ }
140
+ catch {
141
+ }
142
+ }
143
+ try {
144
+ this.ws.send('__heartbeat__');
145
+ }
146
+ catch {
147
+ }
148
+ }
149
+ get readyState() {
150
+ return this.ws?.readyState ?? WebSocket.CLOSED;
151
+ }
152
+ get isConnected() {
153
+ return this.ws?.readyState === WebSocket.OPEN;
154
+ }
155
+ toReadable() {
156
+ if (!this.ws)
157
+ return null;
158
+ const wsAny = this.ws;
159
+ if (wsAny.readable) {
160
+ return webToNodeStream(wsAny.readable);
161
+ }
162
+ return null;
163
+ }
164
+ async pipeFrom(source, options) {
165
+ await this.sendStream(source, options);
166
+ }
167
+ async pipeTo(destination) {
168
+ const readable = this.toReadable();
169
+ if (!readable) {
170
+ throw new StreamError('WebSocket has no readable stream', {
171
+ streamType: 'websocket',
172
+ retriable: false,
173
+ });
174
+ }
175
+ await pipeline(readable, destination);
176
+ }
177
+ async *[Symbol.asyncIterator]() {
178
+ const queue = [];
179
+ let resolveNext = null;
180
+ let closed = false;
181
+ const messageHandler = (msg) => {
182
+ if (resolveNext) {
183
+ resolveNext(msg);
184
+ resolveNext = null;
185
+ }
186
+ else {
187
+ queue.push(msg);
188
+ }
189
+ };
190
+ const closeHandler = () => {
191
+ closed = true;
192
+ if (resolveNext) {
193
+ resolveNext(null);
194
+ resolveNext = null;
195
+ }
196
+ };
197
+ this.on('message', messageHandler);
198
+ this.on('close', closeHandler);
199
+ try {
200
+ while (true) {
201
+ if (queue.length > 0) {
202
+ yield queue.shift();
203
+ }
204
+ else {
205
+ if (closed)
206
+ break;
207
+ const msg = await new Promise((resolve) => {
208
+ resolveNext = resolve;
209
+ });
210
+ if (msg) {
211
+ yield msg;
212
+ }
213
+ else {
214
+ break;
215
+ }
216
+ }
217
+ }
218
+ }
219
+ finally {
220
+ this.off('message', messageHandler);
221
+ this.off('close', closeHandler);
222
+ }
223
+ }
224
+ attemptReconnect() {
225
+ if (this.isReconnecting)
226
+ return;
227
+ if (this.options.maxReconnectAttempts > 0 &&
228
+ this.reconnectAttempts >= this.options.maxReconnectAttempts) {
229
+ this.emit('max-reconnect-attempts');
230
+ return;
231
+ }
232
+ this.isReconnecting = true;
233
+ this.reconnectAttempts++;
234
+ const baseDelay = this.backoff.base * Math.pow(this.backoff.factor, this.reconnectAttempts - 1);
235
+ const capped = this.backoff.max ? Math.min(baseDelay, this.backoff.max) : baseDelay;
236
+ const jittered = this.backoff.jitter ? randomJitter(capped) : capped;
237
+ this.emit('reconnecting', this.reconnectAttempts, jittered);
238
+ this.reconnectTimer = setTimeout(() => {
239
+ this.connect().catch((error) => {
240
+ this.emit('reconnect-error', error);
241
+ });
242
+ }, jittered);
243
+ }
244
+ clearReconnectTimer() {
245
+ if (this.reconnectTimer) {
246
+ clearTimeout(this.reconnectTimer);
247
+ this.reconnectTimer = undefined;
248
+ }
249
+ }
250
+ startHeartbeat() {
251
+ if (this.options.heartbeatInterval <= 0)
252
+ return;
253
+ this.heartbeatTimer = setInterval(() => {
254
+ if (this.isConnected) {
255
+ this.ping();
256
+ this.startPongWatchdog();
257
+ }
258
+ }, this.options.heartbeatInterval);
259
+ }
260
+ stopHeartbeat() {
261
+ if (this.heartbeatTimer) {
262
+ clearInterval(this.heartbeatTimer);
263
+ this.heartbeatTimer = undefined;
264
+ }
265
+ this.stopPongWatchdog();
266
+ }
267
+ startPongWatchdog() {
268
+ this.stopPongWatchdog();
269
+ if (this.options.heartbeatTimeout <= 0)
270
+ return;
271
+ this.pongWatchdog = setTimeout(() => {
272
+ this.emit('heartbeat-timeout');
273
+ if (!this.closedByUser && this.options.reconnect) {
274
+ this.ws?.close(4000, 'heartbeat timeout');
275
+ }
276
+ }, this.options.heartbeatTimeout);
277
+ }
278
+ stopPongWatchdog() {
279
+ if (this.pongWatchdog) {
280
+ clearTimeout(this.pongWatchdog);
281
+ this.pongWatchdog = undefined;
282
+ }
283
+ }
284
+ getBufferedAmount() {
285
+ return this.ws?.bufferedAmount ?? 0;
286
+ }
287
+ async waitForDrain(highWaterMark) {
288
+ const buffered = this.getBufferedAmount();
289
+ if (buffered <= highWaterMark)
290
+ return;
291
+ await new Promise((resolve) => {
292
+ const check = () => {
293
+ if (this.getBufferedAmount() <= highWaterMark || !this.isConnected) {
294
+ resolve();
295
+ }
296
+ else {
297
+ setTimeout(check, 10);
298
+ }
299
+ };
300
+ setTimeout(check, 10);
301
+ });
302
+ }
303
+ }
304
+ export function createWebSocket(url, options) {
305
+ const ws = new ReckerWebSocket(url, options);
306
+ ws.connect().catch(() => {
307
+ });
308
+ return ws;
309
+ }
310
+ function randomJitter(value) {
311
+ const jitter = 0.2 * value;
312
+ return value - jitter + Math.random() * (2 * jitter);
313
+ }
@@ -1 +1,2 @@
1
+ #!/usr/bin/env node
1
2
  export {};
package/dist/cli/index.js CHANGED
@@ -1,7 +1,9 @@
1
+ #!/usr/bin/env node
1
2
  import { program } from 'commander';
2
3
  import { promises as fs } from 'node:fs';
3
4
  import { join } from 'node:path';
4
5
  import colors from '../utils/colors.js';
6
+ import { formatColumns } from '../utils/columns.js';
5
7
  async function readStdin() {
6
8
  if (process.stdin.isTTY) {
7
9
  return null;
@@ -119,7 +121,13 @@ async function main() {
119
121
  }
120
122
  return { method, url, headers, data };
121
123
  }
122
- const PRESET_NAMES = Object.keys(presets).filter(k => k !== 'registry' && !k.startsWith('_'));
124
+ const utilityFunctions = [
125
+ 'registry', 'presetRegistry', 'detectPreset', 'getPreset',
126
+ 'listPresets', 'listAIPresets', 'listCloudPresets', 'listSaaSPresets', 'listDevToolsPresets'
127
+ ];
128
+ const PRESET_NAMES = Object.keys(presets)
129
+ .filter(k => !utilityFunctions.includes(k) && !k.startsWith('_') && typeof presets[k] === 'function')
130
+ .sort();
123
131
  program
124
132
  .name('rek')
125
133
  .description('The HTTP Client for Humans (and Robots)')
@@ -130,7 +138,7 @@ async function main() {
130
138
  .option('-o, --output <file>', 'Write response body to file')
131
139
  .option('-j, --json', 'Force JSON content-type')
132
140
  .option('-e, --env [path]', 'Load .env file from current directory or specified path')
133
- .addHelpText('after', `
141
+ .addHelpText('after', () => `
134
142
  ${colors.bold(colors.yellow('Examples:'))}
135
143
  ${colors.green('$ rek httpbin.org/json')}
136
144
  ${colors.green('$ rek post api.com/users name="Cyber" role="Admin"')}
@@ -138,7 +146,7 @@ ${colors.bold(colors.yellow('Examples:'))}
138
146
  ${colors.green('$ rek @openai/v1/chat/completions model="gpt-5.1"')}
139
147
 
140
148
  ${colors.bold(colors.yellow('Available Presets:'))}
141
- ${colors.cyan(PRESET_NAMES.map(p => '@' + p).join(', '))}
149
+ ${formatColumns(PRESET_NAMES, { prefix: '@', indent: 2, minWidth: 16, transform: colors.cyan })}
142
150
  `)
143
151
  .action(async (args, options) => {
144
152
  if (args.length === 0) {
@@ -380,6 +388,138 @@ ${colors.bold('Details:')}`);
380
388
  process.exit(1);
381
389
  }
382
390
  });
391
+ program
392
+ .command('seo')
393
+ .description('Analyze page SEO (title, meta, headings, links, images, structured data)')
394
+ .argument('<url>', 'URL to analyze')
395
+ .option('-a, --all', 'Show all checks including passed ones')
396
+ .option('--format <format>', 'Output format: text (default) or json', 'text')
397
+ .addHelpText('after', `
398
+ ${colors.bold(colors.yellow('Examples:'))}
399
+ ${colors.green('$ rek seo example.com')} ${colors.gray('Basic SEO analysis')}
400
+ ${colors.green('$ rek seo example.com -a')} ${colors.gray('Show all checks')}
401
+ ${colors.green('$ rek seo example.com --format json')} ${colors.gray('Output as JSON')}
402
+ ${colors.green('$ rek seo example.com --format json | jq')} ${colors.gray('Pipe to jq for processing')}
403
+
404
+ ${colors.bold(colors.yellow('Checks:'))}
405
+ ${colors.cyan('Title Tag')} Length and presence
406
+ ${colors.cyan('Meta Description')} Length and presence
407
+ ${colors.cyan('Headings')} H1 presence and hierarchy
408
+ ${colors.cyan('Images')} Alt text coverage
409
+ ${colors.cyan('Links')} Internal/external distribution
410
+ ${colors.cyan('OpenGraph')} Social sharing meta tags
411
+ ${colors.cyan('Twitter Card')} Twitter sharing meta tags
412
+ ${colors.cyan('Structured Data')} JSON-LD presence
413
+ ${colors.cyan('Technical')} Canonical, viewport, lang
414
+ `)
415
+ .action(async (url, options) => {
416
+ if (!url.startsWith('http'))
417
+ url = `https://${url}`;
418
+ const isJson = options.format === 'json';
419
+ const { createClient } = await import('../core/client.js');
420
+ const { analyzeSeo } = await import('../seo/analyzer.js');
421
+ if (!isJson) {
422
+ console.log(colors.gray(`Analyzing SEO for ${url}...`));
423
+ }
424
+ try {
425
+ const client = createClient({ timeout: 30000 });
426
+ const res = await client.get(url);
427
+ const html = await res.text();
428
+ const report = await analyzeSeo(html, { baseUrl: url });
429
+ if (isJson) {
430
+ const jsonOutput = {
431
+ url,
432
+ analyzedAt: new Date().toISOString(),
433
+ score: report.score,
434
+ grade: report.grade,
435
+ title: report.title,
436
+ metaDescription: report.metaDescription,
437
+ content: report.content,
438
+ headings: report.headings,
439
+ links: report.links,
440
+ images: report.images,
441
+ openGraph: report.social.openGraph,
442
+ twitterCard: report.social.twitterCard,
443
+ jsonLd: report.jsonLd,
444
+ technical: report.technical,
445
+ checks: report.checks,
446
+ summary: {
447
+ total: report.checks.length,
448
+ passed: report.checks.filter(c => c.status === 'pass').length,
449
+ warnings: report.checks.filter(c => c.status === 'warn').length,
450
+ errors: report.checks.filter(c => c.status === 'fail').length,
451
+ info: report.checks.filter(c => c.status === 'info').length,
452
+ },
453
+ };
454
+ console.log(JSON.stringify(jsonOutput, null, 2));
455
+ return;
456
+ }
457
+ let gradeColor = colors.red;
458
+ if (report.grade === 'A')
459
+ gradeColor = colors.green;
460
+ else if (report.grade === 'B')
461
+ gradeColor = colors.blue;
462
+ else if (report.grade === 'C')
463
+ gradeColor = colors.yellow;
464
+ console.log(`
465
+ ${colors.bold(colors.cyan('🔍 SEO Analysis Report'))}
466
+ ${colors.gray('URL:')} ${url}
467
+ ${colors.gray('Grade:')} ${gradeColor(colors.bold(report.grade))} ${colors.gray('Score:')} ${report.score}/100
468
+ `);
469
+ if (report.title) {
470
+ console.log(`${colors.bold('Title:')} ${colors.gray(report.title.text.slice(0, 60))}${report.title.text.length > 60 ? '...' : ''} ${colors.gray(`(${report.title.length} chars)`)}`);
471
+ }
472
+ if (report.metaDescription) {
473
+ console.log(`${colors.bold('Description:')} ${colors.gray(report.metaDescription.text.slice(0, 80))}${report.metaDescription.text.length > 80 ? '...' : ''}`);
474
+ }
475
+ console.log('');
476
+ console.log(`${colors.bold('Content Metrics:')}`);
477
+ console.log(` ${colors.gray('Words:')} ${report.content.wordCount} ${colors.gray('Reading time:')} ~${report.content.readingTimeMinutes} min`);
478
+ console.log(` ${colors.gray('Headings:')} H1×${report.headings.h1Count}, total ${report.headings.structure.length}`);
479
+ console.log(` ${colors.gray('Links:')} ${report.links.total} (${report.links.internal} internal, ${report.links.external} external)`);
480
+ console.log(` ${colors.gray('Images:')} ${report.images.total} (${report.images.withAlt} with alt, ${report.images.withoutAlt} without)`);
481
+ console.log('');
482
+ console.log(`${colors.bold('Checks:')}`);
483
+ const checksToShow = options.all
484
+ ? report.checks
485
+ : report.checks.filter(c => c.status !== 'pass' && c.status !== 'info');
486
+ if (checksToShow.length === 0 && !options.all) {
487
+ console.log(colors.green(' All checks passed! Use -a to see details.'));
488
+ }
489
+ else {
490
+ for (const check of checksToShow) {
491
+ const icon = check.status === 'pass' ? colors.green('✔')
492
+ : check.status === 'warn' ? colors.yellow('⚠')
493
+ : check.status === 'fail' ? colors.red('✖')
494
+ : colors.gray('ℹ');
495
+ const name = colors.bold(check.name.padEnd(18));
496
+ console.log(` ${icon} ${name} ${check.message}`);
497
+ if (check.recommendation && check.status !== 'pass') {
498
+ console.log(` ${colors.gray('→')} ${colors.gray(check.recommendation)}`);
499
+ }
500
+ const evidence = check.evidence;
501
+ if (evidence && check.status !== 'pass') {
502
+ if (evidence.found && Array.isArray(evidence.found) && evidence.found.length > 0) {
503
+ const items = evidence.found.slice(0, 3);
504
+ console.log(` ${colors.gray('Found:')} ${colors.red(items.join(', '))}${evidence.found.length > 3 ? ` (+${evidence.found.length - 3} more)` : ''}`);
505
+ }
506
+ if (evidence.example) {
507
+ console.log(` ${colors.gray('Example:')} ${colors.cyan(evidence.example.split('\n')[0])}`);
508
+ }
509
+ }
510
+ }
511
+ }
512
+ console.log('');
513
+ if (report.jsonLd.count > 0) {
514
+ console.log(`${colors.bold('Structured Data:')} ${report.jsonLd.types.join(', ') || 'Present'}`);
515
+ console.log('');
516
+ }
517
+ }
518
+ catch (error) {
519
+ console.error(colors.red(`SEO analysis failed: ${error.message}`));
520
+ process.exit(1);
521
+ }
522
+ });
383
523
  program
384
524
  .command('ip')
385
525
  .description('Get IP address intelligence using local GeoLite2 database')
@@ -43,6 +43,7 @@ export declare class RekShell {
43
43
  private runWhois;
44
44
  private runTLS;
45
45
  private runSecurityGrader;
46
+ private runSeo;
46
47
  private runIpIntelligence;
47
48
  private runDNS;
48
49
  private runDNSPropagation;