recker 1.0.15 → 1.0.17-next.9570ed5
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 +86 -97
- package/dist/ai/providers/anthropic.d.ts.map +1 -1
- package/dist/ai/providers/anthropic.js +4 -1
- package/dist/ai/providers/base.d.ts.map +1 -1
- package/dist/ai/providers/base.js +7 -2
- package/dist/ai/rate-limiter.d.ts.map +1 -1
- package/dist/ai/rate-limiter.js +4 -1
- package/dist/bench/generator.d.ts.map +1 -1
- package/dist/bench/generator.js +8 -3
- package/dist/bench/stats.d.ts +15 -1
- package/dist/bench/stats.d.ts.map +1 -1
- package/dist/bench/stats.js +117 -5
- package/dist/cache/memory-storage.d.ts.map +1 -1
- package/dist/cache/memory-storage.js +3 -2
- package/dist/cli/handler.js +14 -14
- package/dist/cli/index.js +602 -48
- package/dist/cli/presets.js +5 -5
- package/dist/cli/tui/ai-chat.js +10 -10
- package/dist/cli/tui/load-dashboard.d.ts.map +1 -1
- package/dist/cli/tui/load-dashboard.js +127 -32
- package/dist/cli/tui/scroll-buffer.d.ts +43 -0
- package/dist/cli/tui/scroll-buffer.d.ts.map +1 -0
- package/dist/cli/tui/scroll-buffer.js +162 -0
- package/dist/cli/tui/search-panel.d.ts +41 -0
- package/dist/cli/tui/search-panel.d.ts.map +1 -0
- package/dist/cli/tui/search-panel.js +420 -0
- package/dist/cli/tui/shell.d.ts +14 -0
- package/dist/cli/tui/shell.d.ts.map +1 -1
- package/dist/cli/tui/shell.js +424 -46
- package/dist/cli/tui/websocket.js +17 -17
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +3 -2
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +18 -26
- package/dist/core/errors.d.ts +109 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +214 -1
- package/dist/core/request-promise.d.ts.map +1 -1
- package/dist/core/request-promise.js +5 -6
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +5 -6
- package/dist/dns/index.d.ts +1 -0
- package/dist/dns/index.d.ts.map +1 -1
- package/dist/dns/index.js +1 -0
- package/dist/dns/propagation.d.ts +21 -0
- package/dist/dns/propagation.d.ts.map +1 -0
- package/dist/dns/propagation.js +169 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +10 -11
- package/dist/mcp/embeddings-loader.d.ts +18 -0
- package/dist/mcp/embeddings-loader.d.ts.map +1 -0
- package/dist/mcp/embeddings-loader.js +162 -0
- package/dist/mcp/geoip-loader.d.ts +11 -0
- package/dist/mcp/geoip-loader.d.ts.map +1 -0
- package/dist/mcp/geoip-loader.js +107 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/ip-intel.d.ts +28 -0
- package/dist/mcp/ip-intel.d.ts.map +1 -0
- package/dist/mcp/ip-intel.js +209 -0
- package/dist/mcp/search/hybrid-search.d.ts.map +1 -1
- package/dist/mcp/search/hybrid-search.js +59 -38
- package/dist/mcp/search/math.d.ts.map +1 -1
- package/dist/mcp/search/math.js +5 -1
- package/dist/mcp/server.d.ts +6 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +122 -2
- package/dist/plugins/compression.js +4 -2
- package/dist/plugins/har-player.d.ts.map +1 -1
- package/dist/plugins/har-player.js +8 -11
- package/dist/plugins/odata.d.ts.map +1 -1
- package/dist/plugins/odata.js +5 -2
- package/dist/presets/index.d.ts +2 -0
- package/dist/presets/index.d.ts.map +1 -1
- package/dist/presets/index.js +2 -0
- package/dist/presets/mailgun.d.ts +8 -0
- package/dist/presets/mailgun.d.ts.map +1 -0
- package/dist/presets/mailgun.js +20 -0
- package/dist/presets/registry.d.ts.map +1 -1
- package/dist/presets/registry.js +20 -0
- package/dist/presets/sinch.d.ts +10 -0
- package/dist/presets/sinch.d.ts.map +1 -0
- package/dist/presets/sinch.js +39 -0
- package/dist/protocols/ftp.d.ts.map +1 -1
- package/dist/protocols/ftp.js +69 -16
- package/dist/protocols/sftp.d.ts.map +1 -1
- package/dist/protocols/sftp.js +13 -3
- package/dist/protocols/telnet.d.ts.map +1 -1
- package/dist/protocols/telnet.js +25 -6
- package/dist/recker.d.ts +47 -0
- package/dist/recker.d.ts.map +1 -0
- package/dist/recker.js +99 -0
- package/dist/transport/base-udp.d.ts.map +1 -1
- package/dist/transport/base-udp.js +7 -4
- package/dist/transport/udp-response.d.ts.map +1 -1
- package/dist/transport/udp-response.js +10 -3
- package/dist/transport/udp.d.ts.map +1 -1
- package/dist/transport/udp.js +5 -1
- package/dist/transport/undici.d.ts.map +1 -1
- package/dist/transport/undici.js +75 -63
- package/dist/utils/agent-manager.d.ts +1 -0
- package/dist/utils/agent-manager.d.ts.map +1 -1
- package/dist/utils/agent-manager.js +11 -0
- package/dist/utils/client-pool.d.ts.map +1 -1
- package/dist/utils/client-pool.js +4 -1
- package/dist/utils/colors.d.ts +16 -0
- package/dist/utils/colors.d.ts.map +1 -1
- package/dist/utils/colors.js +16 -0
- package/dist/utils/dns-toolkit.d.ts +88 -1
- package/dist/utils/dns-toolkit.d.ts.map +1 -1
- package/dist/utils/dns-toolkit.js +704 -6
- package/dist/utils/doh.d.ts.map +1 -1
- package/dist/utils/doh.js +13 -16
- package/dist/utils/download.d.ts.map +1 -1
- package/dist/utils/download.js +10 -11
- package/dist/utils/rdap.d.ts +9 -0
- package/dist/utils/rdap.d.ts.map +1 -1
- package/dist/utils/rdap.js +78 -9
- package/dist/utils/security-grader.d.ts +47 -0
- package/dist/utils/security-grader.d.ts.map +1 -0
- package/dist/utils/security-grader.js +637 -0
- package/dist/utils/sparkline.d.ts +18 -0
- package/dist/utils/sparkline.d.ts.map +1 -0
- package/dist/utils/sparkline.js +55 -0
- package/dist/utils/sse.d.ts.map +1 -1
- package/dist/utils/sse.js +5 -6
- package/dist/utils/system-metrics.d.ts +26 -0
- package/dist/utils/system-metrics.d.ts.map +1 -0
- package/dist/utils/system-metrics.js +81 -0
- package/dist/utils/tls-inspector.d.ts +6 -0
- package/dist/utils/tls-inspector.d.ts.map +1 -1
- package/dist/utils/tls-inspector.js +35 -1
- package/dist/webrtc/index.d.ts.map +1 -1
- package/dist/webrtc/index.js +21 -7
- package/dist/websocket/client.d.ts.map +1 -1
- package/dist/websocket/client.js +13 -16
- package/package.json +4 -3
- package/dist/mcp/data/embeddings.json +0 -1
package/dist/cli/presets.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as presets from '../presets/index.js';
|
|
2
|
-
import
|
|
2
|
+
import colors from '../utils/colors.js';
|
|
3
3
|
const ENV_MAPPING = {
|
|
4
4
|
openai: ['OPENAI_API_KEY'],
|
|
5
5
|
anthropic: ['ANTHROPIC_API_KEY'],
|
|
@@ -14,7 +14,7 @@ const ENV_MAPPING = {
|
|
|
14
14
|
export function resolvePreset(name) {
|
|
15
15
|
const presetFn = presets[name];
|
|
16
16
|
if (!presetFn) {
|
|
17
|
-
console.error(
|
|
17
|
+
console.error(colors.red(`Error: Unknown preset '@${name}'`));
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
const requiredEnvs = ENV_MAPPING[name];
|
|
@@ -24,7 +24,7 @@ export function resolvePreset(name) {
|
|
|
24
24
|
for (const envVar of requiredEnvs) {
|
|
25
25
|
const value = process.env[envVar];
|
|
26
26
|
if (!value) {
|
|
27
|
-
console.error(
|
|
27
|
+
console.error(colors.yellow(`Warning: Missing env variable ${envVar} for preset @${name}`));
|
|
28
28
|
missing = true;
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
@@ -33,14 +33,14 @@ export function resolvePreset(name) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
if (missing) {
|
|
36
|
-
console.log(
|
|
36
|
+
console.log(colors.gray(`Tip: export ${requiredEnvs.join('=... ')}=...`));
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
40
|
return presetFn(options);
|
|
41
41
|
}
|
|
42
42
|
catch (error) {
|
|
43
|
-
console.error(
|
|
43
|
+
console.error(colors.red(`Error initializing preset @${name}: ${error.message}`));
|
|
44
44
|
process.exit(1);
|
|
45
45
|
}
|
|
46
46
|
}
|
package/dist/cli/tui/ai-chat.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
|
-
import
|
|
2
|
+
import colors from '../../utils/colors.js';
|
|
3
3
|
import { createAI } from '../../ai/client.js';
|
|
4
4
|
export async function startAIChat(rl, provider = 'openai', apiKey, model) {
|
|
5
5
|
console.clear();
|
|
6
|
-
console.log(
|
|
7
|
-
console.log(
|
|
6
|
+
console.log(colors.bold(colors.magenta(`🤖 Rek AI Chat (${provider})`)));
|
|
7
|
+
console.log(colors.gray('Type your message. Ctrl+C to exit.'));
|
|
8
8
|
const envKey = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY';
|
|
9
9
|
const key = apiKey || process.env[envKey];
|
|
10
10
|
if (!key) {
|
|
11
|
-
console.log(
|
|
11
|
+
console.log(colors.yellow(`
|
|
12
12
|
Warning: No API Key found for ${provider}.`));
|
|
13
|
-
console.log(`Please set it via environment variable ${
|
|
13
|
+
console.log(`Please set it via environment variable ${colors.bold(envKey)} or passing it to the command.`);
|
|
14
14
|
console.log(`Example: set ${envKey}=sk-... inside the shell.`);
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
@@ -24,7 +24,7 @@ Warning: No API Key found for ${provider}.`));
|
|
|
24
24
|
const history = [
|
|
25
25
|
{ role: 'system', content: 'You are Recker AI, a helpful and concise assistant in a terminal environment.' }
|
|
26
26
|
];
|
|
27
|
-
rl.setPrompt(
|
|
27
|
+
rl.setPrompt(colors.magenta('You › '));
|
|
28
28
|
rl.prompt();
|
|
29
29
|
return new Promise((resolve) => {
|
|
30
30
|
const onLine = async (line) => {
|
|
@@ -36,7 +36,7 @@ Warning: No API Key found for ${provider}.`));
|
|
|
36
36
|
if (input.toLowerCase() === '/clear') {
|
|
37
37
|
history.length = 1;
|
|
38
38
|
console.clear();
|
|
39
|
-
console.log(
|
|
39
|
+
console.log(colors.gray('Context cleared.'));
|
|
40
40
|
rl.prompt();
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
@@ -47,7 +47,7 @@ Warning: No API Key found for ${provider}.`));
|
|
|
47
47
|
}
|
|
48
48
|
history.push({ role: 'user', content: input });
|
|
49
49
|
rl.pause();
|
|
50
|
-
process.stdout.write(
|
|
50
|
+
process.stdout.write(colors.cyan('AI › '));
|
|
51
51
|
let fullResponse = '';
|
|
52
52
|
try {
|
|
53
53
|
const stream = await client.stream({
|
|
@@ -66,10 +66,10 @@ Warning: No API Key found for ${provider}.`));
|
|
|
66
66
|
history.push({ role: 'assistant', content: fullResponse });
|
|
67
67
|
}
|
|
68
68
|
catch (error) {
|
|
69
|
-
console.log(
|
|
69
|
+
console.log(colors.red(`
|
|
70
70
|
Error: ${error.message}`));
|
|
71
71
|
if (error.cause)
|
|
72
|
-
console.log(
|
|
72
|
+
console.log(colors.gray(error.cause));
|
|
73
73
|
}
|
|
74
74
|
finally {
|
|
75
75
|
rl.resume();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-dashboard.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/load-dashboard.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,UAAU,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"load-dashboard.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/load-dashboard.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,UAAU,EAAE,MAAM,0BAA0B,CAAC;AASrE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,UAAU,iBAuG1D"}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import colors from '../../utils/colors.js';
|
|
2
2
|
import { plot } from '../../utils/chart.js';
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
import { LoadGenerator } from '../../bench/generator.js';
|
|
5
|
+
import { SparklineBuffer } from '../../utils/sparkline.js';
|
|
6
|
+
import { SystemMetrics } from '../../utils/system-metrics.js';
|
|
5
7
|
const ALTERNATE_SCREEN_ENTER = '\x1b[?1049h';
|
|
6
8
|
const ALTERNATE_SCREEN_EXIT = '\x1b[?1049l';
|
|
9
|
+
const SPARKLINE_WIDTH = 40;
|
|
7
10
|
export async function startLoadDashboard(config) {
|
|
8
11
|
process.stdout.write(ALTERNATE_SCREEN_ENTER);
|
|
9
12
|
readline.emitKeypressEvents(process.stdin);
|
|
@@ -12,6 +15,22 @@ export async function startLoadDashboard(config) {
|
|
|
12
15
|
process.stdin.resume();
|
|
13
16
|
}
|
|
14
17
|
const generator = new LoadGenerator(config);
|
|
18
|
+
const sysMetrics = new SystemMetrics();
|
|
19
|
+
const cpuBuffer = new SparklineBuffer(SPARKLINE_WIDTH);
|
|
20
|
+
const memBuffer = new SparklineBuffer(SPARKLINE_WIDTH);
|
|
21
|
+
let currentCpu = 0;
|
|
22
|
+
let currentMem = { percent: 0, used: 0, total: 0 };
|
|
23
|
+
sysMetrics.onSnapshot((snap) => {
|
|
24
|
+
cpuBuffer.push(snap.cpu);
|
|
25
|
+
memBuffer.push(snap.memory);
|
|
26
|
+
currentCpu = snap.cpu;
|
|
27
|
+
currentMem = {
|
|
28
|
+
percent: snap.memory,
|
|
29
|
+
used: snap.memoryUsed,
|
|
30
|
+
total: snap.memoryTotal
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
sysMetrics.startPolling(1000);
|
|
15
34
|
let abortReject;
|
|
16
35
|
const abortPromise = new Promise((_, reject) => {
|
|
17
36
|
abortReject = reject;
|
|
@@ -19,6 +38,7 @@ export async function startLoadDashboard(config) {
|
|
|
19
38
|
const onKeypress = (_str, key) => {
|
|
20
39
|
if (key && (key.name === 'escape' || (key.ctrl && key.name === 'c'))) {
|
|
21
40
|
generator.stop();
|
|
41
|
+
sysMetrics.stopPolling();
|
|
22
42
|
if (abortReject)
|
|
23
43
|
abortReject(new Error('User aborted'));
|
|
24
44
|
}
|
|
@@ -43,7 +63,7 @@ export async function startLoadDashboard(config) {
|
|
|
43
63
|
latencyHistory.push(snapshot.p95);
|
|
44
64
|
usersHistory.shift();
|
|
45
65
|
usersHistory.push(snapshot.activeUsers);
|
|
46
|
-
render(config, elapsed, remaining, snapshot, rpsHistory, latencyHistory, usersHistory, generator.stats);
|
|
66
|
+
render(config, elapsed, remaining, snapshot, rpsHistory, latencyHistory, usersHistory, generator.stats, cpuBuffer, memBuffer, currentCpu, currentMem);
|
|
47
67
|
}, 1000);
|
|
48
68
|
try {
|
|
49
69
|
await Promise.race([runPromise, abortPromise]);
|
|
@@ -54,6 +74,7 @@ export async function startLoadDashboard(config) {
|
|
|
54
74
|
}
|
|
55
75
|
finally {
|
|
56
76
|
clearInterval(interval);
|
|
77
|
+
sysMetrics.stopPolling();
|
|
57
78
|
process.stdin.off('keypress', onKeypress);
|
|
58
79
|
if (process.stdin.isTTY)
|
|
59
80
|
process.stdin.setRawMode(false);
|
|
@@ -61,57 +82,131 @@ export async function startLoadDashboard(config) {
|
|
|
61
82
|
}
|
|
62
83
|
renderFinalReport(generator.stats, config);
|
|
63
84
|
}
|
|
64
|
-
function render(config, elapsed, remaining, snapshot, rpsHistory, latencyHistory, usersHistory, stats) {
|
|
85
|
+
function render(config, elapsed, remaining, snapshot, rpsHistory, latencyHistory, usersHistory, stats, cpuBuffer, memBuffer, currentCpu, currentMem) {
|
|
65
86
|
readline.cursorTo(process.stdout, 0, 0);
|
|
66
87
|
readline.clearScreenDown(process.stdout);
|
|
67
|
-
console.log(
|
|
68
|
-
console.log(
|
|
69
|
-
console.log(
|
|
70
|
-
console.log(
|
|
88
|
+
console.log(colors.bold(colors.cyan('🔥 Rek Load Generator')));
|
|
89
|
+
console.log(colors.gray(`Target: ${config.url}`));
|
|
90
|
+
console.log(colors.gray(`Mode: ${config.mode.toUpperCase()} ${config.http2 ? '(HTTP/2)' : ''}`));
|
|
91
|
+
console.log(colors.gray('Press ESC to stop'));
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(`${colors.white('Time:')} ${colors.green(elapsed + 's')} ` +
|
|
94
|
+
`${colors.gray('/')} ${config.duration}s ` +
|
|
95
|
+
`${colors.gray('(')}${colors.yellow(remaining + 's left')}${colors.gray(')')} ` +
|
|
96
|
+
`${colors.white('Reqs:')} ${colors.bold(String(stats.totalRequests))}`);
|
|
97
|
+
console.log(`${colors.blue('Users:')} ${colors.bold(String(snapshot.activeUsers))} ` +
|
|
98
|
+
`${colors.green('RPS:')} ${colors.bold(snapshot.rps.toFixed(0))} ` +
|
|
99
|
+
`${colors.magenta('Latency (P95):')} ${colors.bold(snapshot.p95.toFixed(0) + 'ms')} ` +
|
|
100
|
+
`${colors.white('Errors:')} ${stats.failed > 0 ? colors.red(String(stats.failed)) : colors.green('0')}`);
|
|
101
|
+
console.log(colors.gray('──────────────────────────────────────────────────'));
|
|
102
|
+
console.log(colors.bold(colors.blue('👥 Active Users')));
|
|
103
|
+
console.log(colors.blue(plot(usersHistory, { height: 4 })));
|
|
71
104
|
console.log('');
|
|
72
|
-
console.log(
|
|
73
|
-
|
|
74
|
-
`${pc.gray('(')}${pc.yellow(remaining + 's left')}${pc.gray(')')} ` +
|
|
75
|
-
`${pc.white('Reqs:')} ${pc.bold(stats.totalRequests)}`);
|
|
76
|
-
console.log(`${pc.blue('Users:')} ${pc.bold(snapshot.activeUsers)} ` +
|
|
77
|
-
`${pc.green('RPS:')} ${pc.bold(snapshot.rps.toFixed(0))} ` +
|
|
78
|
-
`${pc.magenta('Latency (P95):')} ${pc.bold(snapshot.p95.toFixed(0) + 'ms')} ` +
|
|
79
|
-
`${pc.white('Errors:')} ${stats.failed > 0 ? pc.red(stats.failed) : pc.green('0')}`);
|
|
80
|
-
console.log(pc.gray('──────────────────────────────────────────────────'));
|
|
81
|
-
console.log(pc.bold(pc.blue('👥 Active Users')));
|
|
82
|
-
console.log(pc.blue(plot(usersHistory, { height: 4 })));
|
|
105
|
+
console.log(colors.bold(colors.green('⚡ Requests per Second')));
|
|
106
|
+
console.log(colors.green(plot(rpsHistory, { height: 6 })));
|
|
83
107
|
console.log('');
|
|
84
|
-
console.log(
|
|
85
|
-
console.log(
|
|
108
|
+
console.log(colors.bold(colors.magenta('⏱️ Latency P95 (ms)')));
|
|
109
|
+
console.log(colors.magenta(plot(latencyHistory, { height: 4 })));
|
|
86
110
|
console.log('');
|
|
87
|
-
console.log(
|
|
88
|
-
|
|
111
|
+
console.log(colors.bold(colors.yellow('💻 System Resources')));
|
|
112
|
+
const cpuSparkline = cpuBuffer.render({ min: 0, max: 100 });
|
|
113
|
+
const memSparkline = memBuffer.render({ min: 0, max: 100 });
|
|
114
|
+
const memUsed = SystemMetrics.formatBytes(currentMem.used);
|
|
115
|
+
const memTotal = SystemMetrics.formatBytes(currentMem.total);
|
|
116
|
+
console.log(` ${colors.yellow('CPU')} ${colors.gray(cpuSparkline)} ${colors.bold(currentCpu.toFixed(0) + '%')}`);
|
|
117
|
+
console.log(` ${colors.yellow('RAM')} ${colors.gray(memSparkline)} ${colors.bold(currentMem.percent.toFixed(0) + '%')} ${colors.gray(`(${memUsed}/${memTotal})`)}`);
|
|
118
|
+
const recentErrors = stats.getRecentErrors();
|
|
119
|
+
if (recentErrors.length > 0) {
|
|
120
|
+
console.log('');
|
|
121
|
+
console.log(colors.bold(colors.red('⚠️ Recent Errors')));
|
|
122
|
+
renderErrorList(recentErrors, 5);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function formatErrorEntry(entry) {
|
|
126
|
+
const count = colors.white(`${entry.count}x`);
|
|
127
|
+
if (entry.status === 0) {
|
|
128
|
+
return ` ${count} ${colors.red('ERR')} ${colors.gray(entry.message)}`;
|
|
129
|
+
}
|
|
130
|
+
const statusColor = entry.status >= 500 ? colors.red : colors.yellow;
|
|
131
|
+
return ` ${count} ${statusColor(String(entry.status))} ${colors.gray(entry.message)}`;
|
|
132
|
+
}
|
|
133
|
+
function formatStatusBadge(status) {
|
|
134
|
+
const code = String(status);
|
|
135
|
+
if (status >= 500)
|
|
136
|
+
return colors.bgRed(colors.white(` ${code} `));
|
|
137
|
+
if (status >= 400)
|
|
138
|
+
return colors.bgYellow(colors.black(` ${code} `));
|
|
139
|
+
if (status >= 300)
|
|
140
|
+
return colors.bgCyan(colors.black(` ${code} `));
|
|
141
|
+
return colors.bgGreen(colors.black(` ${code} `));
|
|
142
|
+
}
|
|
143
|
+
function renderErrorList(errors, maxItems = 10) {
|
|
144
|
+
const toShow = errors.slice(-maxItems);
|
|
145
|
+
for (const entry of toShow) {
|
|
146
|
+
console.log(formatErrorEntry(entry));
|
|
147
|
+
}
|
|
148
|
+
if (errors.length > maxItems) {
|
|
149
|
+
console.log(colors.gray(` ... and ${errors.length - maxItems} more`));
|
|
150
|
+
}
|
|
89
151
|
}
|
|
90
152
|
function renderFinalReport(stats, config) {
|
|
91
153
|
const summary = stats.getSummary();
|
|
92
|
-
console.log(
|
|
93
|
-
console.log(
|
|
154
|
+
console.log(colors.bold(colors.green('\n✅ Load Test Complete')));
|
|
155
|
+
console.log(colors.bold('Configuration:'));
|
|
94
156
|
console.log(` URL: ${config.url}`);
|
|
95
157
|
console.log(` Mode: ${config.mode}`);
|
|
96
158
|
console.log(` Users: ${config.users}`);
|
|
97
159
|
console.log(` Duration: ${config.duration}s`);
|
|
98
|
-
console.log('\n' +
|
|
160
|
+
console.log('\n' + colors.bold('Traffic:'));
|
|
99
161
|
console.log(` Total Requests: ${summary.total}`);
|
|
100
|
-
console.log(` Successful: ${
|
|
101
|
-
console.log(` Failed: ${summary.failed > 0 ?
|
|
162
|
+
console.log(` Successful: ${colors.green(String(summary.success))}`);
|
|
163
|
+
console.log(` Failed: ${summary.failed > 0 ? colors.red(String(summary.failed)) : colors.gray('0')}`);
|
|
102
164
|
console.log(` Total Bytes: ${(summary.bytes / 1024 / 1024).toFixed(2)} MB`);
|
|
103
|
-
console.log('\n' +
|
|
165
|
+
console.log('\n' + colors.bold('Latency (ms):'));
|
|
104
166
|
console.log(` Avg: ${summary.latency.avg.toFixed(2)}`);
|
|
105
167
|
console.log(` P50: ${summary.latency.p50.toFixed(0)}`);
|
|
106
168
|
console.log(` P95: ${summary.latency.p95.toFixed(0)}`);
|
|
107
169
|
console.log(` P99: ${summary.latency.p99.toFixed(0)}`);
|
|
108
170
|
console.log(` Max: ${summary.latency.max.toFixed(0)}`);
|
|
109
171
|
if (Object.keys(summary.codes).length > 0) {
|
|
110
|
-
console.log('\n' +
|
|
111
|
-
Object.entries(summary.codes)
|
|
112
|
-
|
|
113
|
-
|
|
172
|
+
console.log('\n' + colors.bold('Status Codes:'));
|
|
173
|
+
const codeEntries = Object.entries(summary.codes)
|
|
174
|
+
.sort(([a], [b]) => Number(a) - Number(b))
|
|
175
|
+
.map(([code, count]) => {
|
|
176
|
+
const c = Number(code);
|
|
177
|
+
const color = c >= 500 ? colors.red : c >= 400 ? colors.yellow : colors.green;
|
|
178
|
+
return `${color(code)}: ${count}`;
|
|
114
179
|
});
|
|
180
|
+
console.log(` ${codeEntries.join(' ')}`);
|
|
181
|
+
}
|
|
182
|
+
const allErrors = stats.getErrors();
|
|
183
|
+
if (allErrors.length > 0) {
|
|
184
|
+
console.log('\n' + colors.bold(colors.red('Errors:')));
|
|
185
|
+
const httpErrors = allErrors.filter(e => e.status > 0);
|
|
186
|
+
const netErrors = allErrors.filter(e => e.status === 0);
|
|
187
|
+
if (httpErrors.length > 0) {
|
|
188
|
+
const httpLine = httpErrors
|
|
189
|
+
.slice(0, 5)
|
|
190
|
+
.map(e => {
|
|
191
|
+
const color = e.status >= 500 ? colors.red : colors.yellow;
|
|
192
|
+
return `${e.count}x ${color(String(e.status))} ${colors.gray(e.message)}`;
|
|
193
|
+
})
|
|
194
|
+
.join(colors.gray(', '));
|
|
195
|
+
console.log(` ${httpLine}`);
|
|
196
|
+
if (httpErrors.length > 5) {
|
|
197
|
+
console.log(colors.gray(` ... +${httpErrors.length - 5} more HTTP errors`));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (netErrors.length > 0) {
|
|
201
|
+
const netLine = netErrors
|
|
202
|
+
.slice(0, 5)
|
|
203
|
+
.map(e => `${e.count}x ${colors.red('ERR')} ${colors.gray(e.message)}`)
|
|
204
|
+
.join(colors.gray(', '));
|
|
205
|
+
console.log(` ${netLine}`);
|
|
206
|
+
if (netErrors.length > 5) {
|
|
207
|
+
console.log(colors.gray(` ... +${netErrors.length - 5} more network errors`));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
115
210
|
}
|
|
116
211
|
console.log('');
|
|
117
212
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
export interface ScrollBufferOptions {
|
|
3
|
+
maxLines?: number;
|
|
4
|
+
viewportHeight?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class ScrollBuffer extends EventEmitter {
|
|
7
|
+
private lines;
|
|
8
|
+
private scrollOffset;
|
|
9
|
+
private maxLines;
|
|
10
|
+
private viewportHeight;
|
|
11
|
+
private isScrollMode;
|
|
12
|
+
private originalWrite;
|
|
13
|
+
private pendingOutput;
|
|
14
|
+
constructor(options?: ScrollBufferOptions);
|
|
15
|
+
write(content: string): void;
|
|
16
|
+
flush(): void;
|
|
17
|
+
get lineCount(): number;
|
|
18
|
+
get position(): number;
|
|
19
|
+
get isScrolledUp(): boolean;
|
|
20
|
+
scrollUp(lines?: number): boolean;
|
|
21
|
+
scrollDown(lines?: number): boolean;
|
|
22
|
+
pageUp(): boolean;
|
|
23
|
+
pageDown(): boolean;
|
|
24
|
+
scrollToTop(): void;
|
|
25
|
+
scrollToBottom(): void;
|
|
26
|
+
getVisibleLines(): string[];
|
|
27
|
+
render(): string;
|
|
28
|
+
updateViewport(height?: number): void;
|
|
29
|
+
enterScrollMode(): void;
|
|
30
|
+
exitScrollMode(): void;
|
|
31
|
+
get inScrollMode(): boolean;
|
|
32
|
+
clear(): void;
|
|
33
|
+
getScrollInfo(): {
|
|
34
|
+
current: number;
|
|
35
|
+
total: number;
|
|
36
|
+
percent: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare function parseScrollKey(data: Buffer): 'pageUp' | 'pageDown' | 'scrollUp' | 'scrollDown' | 'home' | 'end' | 'quit' | null;
|
|
40
|
+
export declare function parseMouseScroll(data: Buffer): 'scrollUp' | 'scrollDown' | null;
|
|
41
|
+
export declare function enableMouseReporting(): void;
|
|
42
|
+
export declare function disableMouseReporting(): void;
|
|
43
|
+
//# sourceMappingURL=scroll-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-buffer.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/scroll-buffer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,aAAa,CAAc;gBAEvB,OAAO,GAAE,mBAAwB;IAS7C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAuB5B,KAAK,IAAI,IAAI;IAUb,IAAI,SAAS,IAAI,MAAM,CAEtB;IAKD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAKD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,QAAQ,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAcpC,UAAU,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAatC,MAAM,IAAI,OAAO;IAOjB,QAAQ,IAAI,OAAO;IAOnB,WAAW,IAAI,IAAI;IAOnB,cAAc,IAAI,IAAI;IAOtB,eAAe,IAAI,MAAM,EAAE;IAS3B,MAAM,IAAI,MAAM;IAsBhB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAOrC,eAAe,IAAI,IAAI;IAcvB,cAAc,IAAI,IAAI;IAWtB,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,KAAK,IAAI,IAAI;IASb,aAAa,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAOrE;AAKD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAyB/H;AAOD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,IAAI,CAqB/E;AAKD,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAKD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
export class ScrollBuffer extends EventEmitter {
|
|
3
|
+
lines = [];
|
|
4
|
+
scrollOffset = 0;
|
|
5
|
+
maxLines;
|
|
6
|
+
viewportHeight;
|
|
7
|
+
isScrollMode = false;
|
|
8
|
+
originalWrite = null;
|
|
9
|
+
pendingOutput = '';
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
super();
|
|
12
|
+
this.maxLines = options.maxLines || 10000;
|
|
13
|
+
this.viewportHeight = options.viewportHeight || (process.stdout.rows || 24) - 2;
|
|
14
|
+
}
|
|
15
|
+
write(content) {
|
|
16
|
+
const parts = (this.pendingOutput + content).split('\n');
|
|
17
|
+
this.pendingOutput = parts.pop() || '';
|
|
18
|
+
for (const part of parts) {
|
|
19
|
+
this.lines.push(part);
|
|
20
|
+
}
|
|
21
|
+
if (this.lines.length > this.maxLines) {
|
|
22
|
+
this.lines = this.lines.slice(-this.maxLines);
|
|
23
|
+
}
|
|
24
|
+
if (this.scrollOffset === 0 && !this.isScrollMode) {
|
|
25
|
+
this.emit('output', content);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
flush() {
|
|
29
|
+
if (this.pendingOutput) {
|
|
30
|
+
this.lines.push(this.pendingOutput);
|
|
31
|
+
this.pendingOutput = '';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
get lineCount() {
|
|
35
|
+
return this.lines.length;
|
|
36
|
+
}
|
|
37
|
+
get position() {
|
|
38
|
+
return this.scrollOffset;
|
|
39
|
+
}
|
|
40
|
+
get isScrolledUp() {
|
|
41
|
+
return this.scrollOffset > 0;
|
|
42
|
+
}
|
|
43
|
+
scrollUp(lines = 1) {
|
|
44
|
+
const maxScroll = Math.max(0, this.lines.length - this.viewportHeight);
|
|
45
|
+
const newOffset = Math.min(this.scrollOffset + lines, maxScroll);
|
|
46
|
+
if (newOffset !== this.scrollOffset) {
|
|
47
|
+
this.scrollOffset = newOffset;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
scrollDown(lines = 1) {
|
|
53
|
+
const newOffset = Math.max(0, this.scrollOffset - lines);
|
|
54
|
+
if (newOffset !== this.scrollOffset) {
|
|
55
|
+
this.scrollOffset = newOffset;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
pageUp() {
|
|
61
|
+
return this.scrollUp(this.viewportHeight - 1);
|
|
62
|
+
}
|
|
63
|
+
pageDown() {
|
|
64
|
+
return this.scrollDown(this.viewportHeight - 1);
|
|
65
|
+
}
|
|
66
|
+
scrollToTop() {
|
|
67
|
+
this.scrollOffset = Math.max(0, this.lines.length - this.viewportHeight);
|
|
68
|
+
}
|
|
69
|
+
scrollToBottom() {
|
|
70
|
+
this.scrollOffset = 0;
|
|
71
|
+
}
|
|
72
|
+
getVisibleLines() {
|
|
73
|
+
const endIndex = this.lines.length - this.scrollOffset;
|
|
74
|
+
const startIndex = Math.max(0, endIndex - this.viewportHeight);
|
|
75
|
+
return this.lines.slice(startIndex, endIndex);
|
|
76
|
+
}
|
|
77
|
+
render() {
|
|
78
|
+
const visibleLines = this.getVisibleLines();
|
|
79
|
+
let output = '';
|
|
80
|
+
output += '\x1b[2J\x1b[H';
|
|
81
|
+
output += visibleLines.join('\n');
|
|
82
|
+
if (this.scrollOffset > 0) {
|
|
83
|
+
const indicator = `\x1b[7m ↑ ${this.scrollOffset} lines above | Page Down to scroll ↓ \x1b[0m`;
|
|
84
|
+
output += `\n${indicator}`;
|
|
85
|
+
}
|
|
86
|
+
return output;
|
|
87
|
+
}
|
|
88
|
+
updateViewport(height) {
|
|
89
|
+
this.viewportHeight = height || (process.stdout.rows || 24) - 2;
|
|
90
|
+
}
|
|
91
|
+
enterScrollMode() {
|
|
92
|
+
if (this.isScrollMode)
|
|
93
|
+
return;
|
|
94
|
+
this.isScrollMode = true;
|
|
95
|
+
this.flush();
|
|
96
|
+
this.emit('scrollModeStart');
|
|
97
|
+
}
|
|
98
|
+
exitScrollMode() {
|
|
99
|
+
if (!this.isScrollMode)
|
|
100
|
+
return;
|
|
101
|
+
this.isScrollMode = false;
|
|
102
|
+
this.scrollToBottom();
|
|
103
|
+
this.emit('scrollModeEnd');
|
|
104
|
+
}
|
|
105
|
+
get inScrollMode() {
|
|
106
|
+
return this.isScrollMode;
|
|
107
|
+
}
|
|
108
|
+
clear() {
|
|
109
|
+
this.lines = [];
|
|
110
|
+
this.scrollOffset = 0;
|
|
111
|
+
this.pendingOutput = '';
|
|
112
|
+
}
|
|
113
|
+
getScrollInfo() {
|
|
114
|
+
const total = this.lines.length;
|
|
115
|
+
const current = total - this.scrollOffset;
|
|
116
|
+
const percent = total > 0 ? Math.round((current / total) * 100) : 100;
|
|
117
|
+
return { current, total, percent };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export function parseScrollKey(data) {
|
|
121
|
+
const str = data.toString();
|
|
122
|
+
if (str === '\x1b[5~' || str === '\x1bOy')
|
|
123
|
+
return 'pageUp';
|
|
124
|
+
if (str === '\x1b[6~' || str === '\x1bOs')
|
|
125
|
+
return 'pageDown';
|
|
126
|
+
if (str === '\x1b[1;2A')
|
|
127
|
+
return 'scrollUp';
|
|
128
|
+
if (str === '\x1b[1;2B')
|
|
129
|
+
return 'scrollDown';
|
|
130
|
+
if (str === '\x1b[H' || str === '\x1b[1~' || str === '\x1bOH')
|
|
131
|
+
return 'home';
|
|
132
|
+
if (str === '\x1b[F' || str === '\x1b[4~' || str === '\x1bOF')
|
|
133
|
+
return 'end';
|
|
134
|
+
if (str === 'q' || str === 'Q')
|
|
135
|
+
return 'quit';
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
export function parseMouseScroll(data) {
|
|
139
|
+
const str = data.toString();
|
|
140
|
+
const sgrMatch = str.match(/\x1b\[<(\d+);(\d+);(\d+)[Mm]/);
|
|
141
|
+
if (sgrMatch) {
|
|
142
|
+
const button = parseInt(sgrMatch[1], 10);
|
|
143
|
+
if (button === 64)
|
|
144
|
+
return 'scrollUp';
|
|
145
|
+
if (button === 65)
|
|
146
|
+
return 'scrollDown';
|
|
147
|
+
}
|
|
148
|
+
if (data.length >= 6 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x4d) {
|
|
149
|
+
const button = data[3];
|
|
150
|
+
if (button === 96 || button === 0x60)
|
|
151
|
+
return 'scrollUp';
|
|
152
|
+
if (button === 97 || button === 0x61)
|
|
153
|
+
return 'scrollDown';
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
export function enableMouseReporting() {
|
|
158
|
+
process.stdout.write('\x1b[?1000h\x1b[?1006h');
|
|
159
|
+
}
|
|
160
|
+
export function disableMouseReporting() {
|
|
161
|
+
process.stdout.write('\x1b[?1000l\x1b[?1006l');
|
|
162
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
interface SearchPanelOptions {
|
|
2
|
+
initialQuery?: string;
|
|
3
|
+
docsPath?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class SearchPanel {
|
|
6
|
+
private rl;
|
|
7
|
+
private state;
|
|
8
|
+
private docsPath;
|
|
9
|
+
private running;
|
|
10
|
+
private termWidth;
|
|
11
|
+
private termHeight;
|
|
12
|
+
private leftPanelWidth;
|
|
13
|
+
private rightPanelWidth;
|
|
14
|
+
private contentHeight;
|
|
15
|
+
private previewContent;
|
|
16
|
+
constructor(options?: SearchPanelOptions);
|
|
17
|
+
private findDocsPath;
|
|
18
|
+
open(): Promise<void>;
|
|
19
|
+
close(): void;
|
|
20
|
+
private updateDimensions;
|
|
21
|
+
private handleKeyInput;
|
|
22
|
+
private navigateUp;
|
|
23
|
+
private navigateDown;
|
|
24
|
+
private scrollPreview;
|
|
25
|
+
private performSearch;
|
|
26
|
+
private loadPreview;
|
|
27
|
+
private formatMarkdown;
|
|
28
|
+
private wordWrap;
|
|
29
|
+
private stripAnsi;
|
|
30
|
+
private render;
|
|
31
|
+
private renderHeader;
|
|
32
|
+
private renderSearchInput;
|
|
33
|
+
private renderDivider;
|
|
34
|
+
private renderContent;
|
|
35
|
+
private renderLeftPanelLine;
|
|
36
|
+
private renderRightPanelLine;
|
|
37
|
+
private renderFooter;
|
|
38
|
+
}
|
|
39
|
+
export declare function openSearchPanel(query?: string): Promise<void>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=search-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-panel.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/search-panel.ts"],"names":[],"mappings":"AAyCA,UAAU,kBAAkB;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAeD,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAgB;gBAE1B,OAAO,GAAE,kBAAuB;IAa5C,OAAO,CAAC,YAAY;IAed,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiD3B,KAAK,IAAI,IAAI;IAsBb,OAAO,CAAC,gBAAgB;YAYV,cAAc;IA8C5B,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,aAAa;YAKP,aAAa;IAgC3B,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,cAAc;IAyCtB,OAAO,CAAC,QAAQ;IAuBhB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IA2Bd,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,mBAAmB;IAiC3B,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,YAAY;CA0BrB;AAKD,wBAAsB,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnE"}
|