recker 1.0.5 → 1.0.7
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 +1 -1
- package/dist/ai/adaptive-timeout.d.ts +51 -0
- package/dist/ai/adaptive-timeout.d.ts.map +1 -0
- package/dist/ai/adaptive-timeout.js +208 -0
- package/dist/ai/client.d.ts +24 -0
- package/dist/ai/client.d.ts.map +1 -0
- package/dist/ai/client.js +289 -0
- package/dist/ai/index.d.ts +10 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +6 -0
- package/dist/ai/providers/anthropic.d.ts +64 -0
- package/dist/ai/providers/anthropic.d.ts.map +1 -0
- package/dist/ai/providers/anthropic.js +367 -0
- package/dist/ai/providers/base.d.ts +49 -0
- package/dist/ai/providers/base.d.ts.map +1 -0
- package/dist/ai/providers/base.js +145 -0
- package/dist/ai/providers/index.d.ts +7 -0
- package/dist/ai/providers/index.d.ts.map +1 -0
- package/dist/ai/providers/index.js +3 -0
- package/dist/ai/providers/openai.d.ts +65 -0
- package/dist/ai/providers/openai.d.ts.map +1 -0
- package/dist/ai/providers/openai.js +298 -0
- package/dist/ai/rate-limiter.d.ts +44 -0
- package/dist/ai/rate-limiter.d.ts.map +1 -0
- package/dist/ai/rate-limiter.js +212 -0
- package/dist/bench/generator.d.ts +19 -0
- package/dist/bench/generator.d.ts.map +1 -0
- package/dist/bench/generator.js +86 -0
- package/dist/bench/stats.d.ts +35 -0
- package/dist/bench/stats.d.ts.map +1 -0
- package/dist/bench/stats.js +60 -0
- package/dist/cli/handler.d.ts +11 -0
- package/dist/cli/handler.d.ts.map +1 -0
- package/dist/cli/handler.js +92 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +255 -0
- package/dist/cli/presets.d.ts +2 -0
- package/dist/cli/presets.d.ts.map +1 -0
- package/dist/cli/presets.js +67 -0
- package/dist/cli/tui/ai-chat.d.ts +3 -0
- package/dist/cli/tui/ai-chat.d.ts.map +1 -0
- package/dist/cli/tui/ai-chat.js +100 -0
- package/dist/cli/tui/load-dashboard.d.ts +3 -0
- package/dist/cli/tui/load-dashboard.d.ts.map +1 -0
- package/dist/cli/tui/load-dashboard.js +117 -0
- package/dist/cli/tui/shell.d.ts +27 -0
- package/dist/cli/tui/shell.d.ts.map +1 -0
- package/dist/cli/tui/shell.js +386 -0
- package/dist/cli/tui/websocket.d.ts +2 -0
- package/dist/cli/tui/websocket.d.ts.map +1 -0
- package/dist/cli/tui/websocket.js +87 -0
- package/dist/contract/index.d.ts +2 -2
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/core/client.d.ts +1 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +4 -2
- package/dist/core/request-promise.d.ts +1 -1
- package/dist/core/request-promise.d.ts.map +1 -1
- package/dist/mcp/contract.d.ts +1 -1
- package/dist/mcp/contract.d.ts.map +1 -1
- package/dist/protocols/ftp.d.ts +28 -5
- package/dist/protocols/ftp.d.ts.map +1 -1
- package/dist/protocols/ftp.js +549 -136
- package/dist/protocols/sftp.d.ts +4 -2
- package/dist/protocols/sftp.d.ts.map +1 -1
- package/dist/protocols/sftp.js +16 -2
- package/dist/protocols/telnet.d.ts +37 -5
- package/dist/protocols/telnet.d.ts.map +1 -1
- package/dist/protocols/telnet.js +434 -58
- package/dist/scrape/document.d.ts.map +1 -1
- package/dist/scrape/document.js +7 -12
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/mock-udp-server.d.ts +44 -0
- package/dist/testing/mock-udp-server.d.ts.map +1 -0
- package/dist/testing/mock-udp-server.js +188 -0
- package/dist/transport/base-udp.d.ts +36 -0
- package/dist/transport/base-udp.d.ts.map +1 -0
- package/dist/transport/base-udp.js +188 -0
- package/dist/transport/udp-response.d.ts +65 -0
- package/dist/transport/udp-response.d.ts.map +1 -0
- package/dist/transport/udp-response.js +269 -0
- package/dist/transport/udp.d.ts +22 -0
- package/dist/transport/udp.d.ts.map +1 -0
- package/dist/transport/udp.js +260 -0
- package/dist/types/ai.d.ts +268 -0
- package/dist/types/ai.d.ts.map +1 -0
- package/dist/types/ai.js +1 -0
- package/dist/types/udp.d.ts +138 -0
- package/dist/types/udp.d.ts.map +1 -0
- package/dist/types/udp.js +1 -0
- package/dist/udp/index.d.ts +6 -0
- package/dist/udp/index.d.ts.map +1 -0
- package/dist/udp/index.js +3 -0
- package/dist/utils/chart.d.ts +15 -0
- package/dist/utils/chart.d.ts.map +1 -0
- package/dist/utils/chart.js +94 -0
- package/dist/utils/colors.d.ts +27 -0
- package/dist/utils/colors.d.ts.map +1 -0
- package/dist/utils/colors.js +50 -0
- package/dist/utils/optional-require.d.ts +20 -0
- package/dist/utils/optional-require.d.ts.map +1 -0
- package/dist/utils/optional-require.js +105 -0
- package/package.json +53 -12
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare class LoadStats {
|
|
2
|
+
totalRequests: number;
|
|
3
|
+
successful: number;
|
|
4
|
+
failed: number;
|
|
5
|
+
bytesTransferred: number;
|
|
6
|
+
statusCodes: Record<number, number>;
|
|
7
|
+
errors: Record<string, number>;
|
|
8
|
+
latencies: number[];
|
|
9
|
+
private lastSnapshotTime;
|
|
10
|
+
private lastSnapshotRequests;
|
|
11
|
+
activeUsers: number;
|
|
12
|
+
addResult(durationMs: number, status: number, bytes: number, error?: Error): void;
|
|
13
|
+
getSnapshot(): {
|
|
14
|
+
rps: number;
|
|
15
|
+
p95: number;
|
|
16
|
+
activeUsers: number;
|
|
17
|
+
};
|
|
18
|
+
getSummary(): {
|
|
19
|
+
total: number;
|
|
20
|
+
success: number;
|
|
21
|
+
failed: number;
|
|
22
|
+
bytes: number;
|
|
23
|
+
rps: number;
|
|
24
|
+
latency: {
|
|
25
|
+
avg: number;
|
|
26
|
+
p50: number;
|
|
27
|
+
p95: number;
|
|
28
|
+
p99: number;
|
|
29
|
+
max: number;
|
|
30
|
+
};
|
|
31
|
+
codes: Record<number, number>;
|
|
32
|
+
errors: Record<string, number>;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/bench/stats.ts"],"names":[],"mappings":"AAAA,qBAAa,SAAS;IACpB,aAAa,SAAK;IAClB,UAAU,SAAK;IACf,MAAM,SAAK;IACX,gBAAgB,SAAK;IAErB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAGpC,SAAS,EAAE,MAAM,EAAE,CAAM;IAGzB,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,oBAAoB,CAAK;IAGjC,WAAW,SAAK;IAEhB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;IAoB1E,WAAW;;;;;IAmBX,UAAU;;;;;;;;;;;;;;;;CAmBX"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export class LoadStats {
|
|
2
|
+
totalRequests = 0;
|
|
3
|
+
successful = 0;
|
|
4
|
+
failed = 0;
|
|
5
|
+
bytesTransferred = 0;
|
|
6
|
+
statusCodes = {};
|
|
7
|
+
errors = {};
|
|
8
|
+
latencies = [];
|
|
9
|
+
lastSnapshotTime = Date.now();
|
|
10
|
+
lastSnapshotRequests = 0;
|
|
11
|
+
activeUsers = 0;
|
|
12
|
+
addResult(durationMs, status, bytes, error) {
|
|
13
|
+
this.totalRequests++;
|
|
14
|
+
this.bytesTransferred += bytes;
|
|
15
|
+
this.latencies.push(durationMs);
|
|
16
|
+
if (error || status >= 400) {
|
|
17
|
+
this.failed++;
|
|
18
|
+
if (error) {
|
|
19
|
+
const msg = error.message || 'Unknown Error';
|
|
20
|
+
this.errors[msg] = (this.errors[msg] || 0) + 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.successful++;
|
|
25
|
+
}
|
|
26
|
+
if (status) {
|
|
27
|
+
this.statusCodes[status] = (this.statusCodes[status] || 0) + 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
getSnapshot() {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const timeDiff = (now - this.lastSnapshotTime) / 1000;
|
|
33
|
+
const reqDiff = this.totalRequests - this.lastSnapshotRequests;
|
|
34
|
+
const rps = reqDiff / timeDiff;
|
|
35
|
+
this.lastSnapshotTime = now;
|
|
36
|
+
this.lastSnapshotRequests = this.totalRequests;
|
|
37
|
+
const recentLatencies = this.latencies.slice(-reqDiff);
|
|
38
|
+
recentLatencies.sort((a, b) => a - b);
|
|
39
|
+
const p95 = recentLatencies[Math.floor(recentLatencies.length * 0.95)] || 0;
|
|
40
|
+
return { rps, p95, activeUsers: this.activeUsers };
|
|
41
|
+
}
|
|
42
|
+
getSummary() {
|
|
43
|
+
this.latencies.sort((a, b) => a - b);
|
|
44
|
+
const avg = this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length || 0;
|
|
45
|
+
const p50 = this.latencies[Math.floor(this.latencies.length * 0.50)] || 0;
|
|
46
|
+
const p95 = this.latencies[Math.floor(this.latencies.length * 0.95)] || 0;
|
|
47
|
+
const p99 = this.latencies[Math.floor(this.latencies.length * 0.99)] || 0;
|
|
48
|
+
const max = this.latencies[this.latencies.length - 1] || 0;
|
|
49
|
+
return {
|
|
50
|
+
total: this.totalRequests,
|
|
51
|
+
success: this.successful,
|
|
52
|
+
failed: this.failed,
|
|
53
|
+
bytes: this.bytesTransferred,
|
|
54
|
+
rps: Math.round(this.totalRequests / ((Date.now() - this.latencies[0]) / 1000) || 0),
|
|
55
|
+
latency: { avg, p50, p95, p99, max },
|
|
56
|
+
codes: this.statusCodes,
|
|
57
|
+
errors: this.errors
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface RequestOptions {
|
|
2
|
+
method: string;
|
|
3
|
+
url: string;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
body?: any;
|
|
6
|
+
verbose?: boolean;
|
|
7
|
+
presetConfig?: any;
|
|
8
|
+
}
|
|
9
|
+
export declare function handleRequest(options: RequestOptions): Promise<void>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/cli/handler.ts"],"names":[],"mappings":"AAmBA,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,cAAc,iBAqG1D"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createClient } from '../core/client.js';
|
|
2
|
+
import { requireOptional } from '../utils/optional-require.js';
|
|
3
|
+
import pc from '../utils/colors.js';
|
|
4
|
+
import oraImport from 'ora';
|
|
5
|
+
let highlight;
|
|
6
|
+
const ora = oraImport;
|
|
7
|
+
async function initDependencies() {
|
|
8
|
+
if (!highlight) {
|
|
9
|
+
const cardinal = await requireOptional('cardinal', 'recker/cli');
|
|
10
|
+
highlight = cardinal.highlight;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function handleRequest(options) {
|
|
14
|
+
await initDependencies();
|
|
15
|
+
const spinner = ora({
|
|
16
|
+
text: `${pc.bold(options.method)} ${pc.cyan(options.url)}`,
|
|
17
|
+
color: 'cyan',
|
|
18
|
+
spinner: 'dots'
|
|
19
|
+
}).start();
|
|
20
|
+
const start = performance.now();
|
|
21
|
+
try {
|
|
22
|
+
let client;
|
|
23
|
+
if (options.presetConfig) {
|
|
24
|
+
client = createClient(options.presetConfig);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
try {
|
|
28
|
+
const urlObj = new URL(options.url);
|
|
29
|
+
client = createClient({ baseUrl: urlObj.origin });
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
client = createClient();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
let requestBody = undefined;
|
|
36
|
+
if (options.body) {
|
|
37
|
+
requestBody = JSON.stringify(options.body);
|
|
38
|
+
if (!options.headers['Content-Type'] && !options.headers['content-type']) {
|
|
39
|
+
options.headers['Content-Type'] = 'application/json';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const response = await client.request(options.url, {
|
|
43
|
+
method: options.method,
|
|
44
|
+
headers: options.headers,
|
|
45
|
+
body: requestBody
|
|
46
|
+
});
|
|
47
|
+
const duration = Math.round(performance.now() - start);
|
|
48
|
+
spinner.stop();
|
|
49
|
+
const statusColor = response.ok ? pc.green : pc.red;
|
|
50
|
+
console.log(`${statusColor(pc.bold(response.status))} ${statusColor(response.statusText)} ` +
|
|
51
|
+
`${pc.gray(`(${duration}ms)`)}`);
|
|
52
|
+
if (options.verbose) {
|
|
53
|
+
console.log(pc.gray('\n--- Request ---'));
|
|
54
|
+
console.log(`${pc.bold(options.method)} ${options.url}`);
|
|
55
|
+
Object.entries(options.headers).forEach(([k, v]) => {
|
|
56
|
+
console.log(`${pc.blue(k)}: ${v}`);
|
|
57
|
+
});
|
|
58
|
+
if (options.body) {
|
|
59
|
+
console.log(pc.gray('Body:'), JSON.stringify(options.body, null, 2));
|
|
60
|
+
}
|
|
61
|
+
console.log(pc.gray('---------------\n'));
|
|
62
|
+
}
|
|
63
|
+
if (options.verbose) {
|
|
64
|
+
console.log(pc.gray('--- Response Headers ---'));
|
|
65
|
+
response.headers.forEach((value, key) => {
|
|
66
|
+
console.log(`${pc.blue(key)}: ${value}`);
|
|
67
|
+
});
|
|
68
|
+
console.log(pc.gray('------------------------\n'));
|
|
69
|
+
}
|
|
70
|
+
const contentType = response.headers.get('content-type') || '';
|
|
71
|
+
const text = await response.text();
|
|
72
|
+
if (!text)
|
|
73
|
+
return;
|
|
74
|
+
if (contentType.includes('application/json')) {
|
|
75
|
+
try {
|
|
76
|
+
const jsonObj = JSON.parse(text);
|
|
77
|
+
const jsonString = JSON.stringify(jsonObj, null, 2);
|
|
78
|
+
console.log(highlight(jsonString));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
console.log(text);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(text);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
spinner.fail(pc.red('Request Failed'));
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import pc from '../utils/colors.js';
|
|
4
|
+
async function main() {
|
|
5
|
+
const { handleRequest } = await import('./handler.js');
|
|
6
|
+
const { resolvePreset } = await import('./presets.js');
|
|
7
|
+
const presets = await import('../presets/index.js');
|
|
8
|
+
let version = '0.0.0';
|
|
9
|
+
try {
|
|
10
|
+
const pkg = await import('../../package.json', { with: { type: 'json' } });
|
|
11
|
+
version = pkg.default?.version || '0.0.0';
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
}
|
|
15
|
+
function parseMixedArgs(args, hasPreset = false) {
|
|
16
|
+
const headers = {};
|
|
17
|
+
const data = {};
|
|
18
|
+
let method = 'GET';
|
|
19
|
+
let url = '';
|
|
20
|
+
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
|
|
21
|
+
for (const arg of args) {
|
|
22
|
+
if (methods.includes(arg.toUpperCase())) {
|
|
23
|
+
method = arg.toUpperCase();
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (arg.includes(':') && !arg.includes('://') && !arg.includes('=')) {
|
|
27
|
+
const [key, value] = arg.split(':');
|
|
28
|
+
headers[key.trim()] = value.trim();
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (arg.includes('=')) {
|
|
32
|
+
if (method === 'GET')
|
|
33
|
+
method = 'POST';
|
|
34
|
+
const isTyped = arg.includes(':=');
|
|
35
|
+
const separator = isTyped ? ':=' : '=';
|
|
36
|
+
const [key, value] = arg.split(separator);
|
|
37
|
+
if (isTyped) {
|
|
38
|
+
if (value === 'true')
|
|
39
|
+
data[key] = true;
|
|
40
|
+
else if (value === 'false')
|
|
41
|
+
data[key] = false;
|
|
42
|
+
else if (!isNaN(Number(value)))
|
|
43
|
+
data[key] = Number(value);
|
|
44
|
+
else
|
|
45
|
+
data[key] = value;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
data[key] = value;
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!url) {
|
|
53
|
+
url = arg;
|
|
54
|
+
if (!hasPreset && !url.startsWith('http') && !url.startsWith('ws') && !url.startsWith('udp')) {
|
|
55
|
+
url = `https://${url}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { method, url, headers, data };
|
|
60
|
+
}
|
|
61
|
+
const PRESET_NAMES = Object.keys(presets).filter(k => k !== 'registry' && !k.startsWith('_'));
|
|
62
|
+
program
|
|
63
|
+
.name('rek')
|
|
64
|
+
.description('The HTTP Client for Humans (and Robots)')
|
|
65
|
+
.version(version)
|
|
66
|
+
.argument('[args...]', 'URL, Method, Headers (Key:Value), Data (key=value)')
|
|
67
|
+
.option('-v, --verbose', 'Show full request/response details')
|
|
68
|
+
.option('-j, --json', 'Force JSON content-type')
|
|
69
|
+
.addHelpText('after', `
|
|
70
|
+
${pc.bold(pc.yellow('Examples:'))}
|
|
71
|
+
${pc.green('$ rek httpbin.org/json')}
|
|
72
|
+
${pc.green('$ rek post api.com/users name="Cyber" role="Admin"')}
|
|
73
|
+
${pc.green('$ rek @github/user')}
|
|
74
|
+
${pc.green('$ rek @openai/v1/chat/completions model="gpt-5.1"')}
|
|
75
|
+
|
|
76
|
+
${pc.bold(pc.yellow('Available Presets:'))}
|
|
77
|
+
${pc.cyan(PRESET_NAMES.map(p => '@' + p).join(', '))}
|
|
78
|
+
`)
|
|
79
|
+
.action(async (args, options) => {
|
|
80
|
+
if (args.length === 0) {
|
|
81
|
+
program.help();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
let argsToParse = args;
|
|
85
|
+
let presetConfig = undefined;
|
|
86
|
+
if (args[0].startsWith('@')) {
|
|
87
|
+
let presetName = args[0].slice(1);
|
|
88
|
+
let pathFromPreset = '';
|
|
89
|
+
if (presetName.includes('/')) {
|
|
90
|
+
const parts = presetName.split('/');
|
|
91
|
+
presetName = parts[0];
|
|
92
|
+
pathFromPreset = '/' + parts.slice(1).join('/');
|
|
93
|
+
}
|
|
94
|
+
presetConfig = resolvePreset(presetName);
|
|
95
|
+
argsToParse = args.slice(1);
|
|
96
|
+
if (pathFromPreset) {
|
|
97
|
+
argsToParse.unshift(pathFromPreset);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const { method, url, headers, data } = parseMixedArgs(argsToParse, !!presetConfig);
|
|
101
|
+
if (!url) {
|
|
102
|
+
console.error(pc.red('Error: URL/Path is required'));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
if (options.json) {
|
|
106
|
+
headers['Content-Type'] = 'application/json';
|
|
107
|
+
headers['Accept'] = 'application/json';
|
|
108
|
+
}
|
|
109
|
+
if (url.startsWith('ws://') || url.startsWith('wss://')) {
|
|
110
|
+
const { startInteractiveWebSocket } = await import('./tui/websocket.js');
|
|
111
|
+
await startInteractiveWebSocket(url, headers);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (url.startsWith('udp://')) {
|
|
115
|
+
console.log(pc.yellow('UDP mode coming soon...'));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await handleRequest({
|
|
120
|
+
method,
|
|
121
|
+
url,
|
|
122
|
+
headers,
|
|
123
|
+
body: Object.keys(data).length > 0 ? data : undefined,
|
|
124
|
+
verbose: options.verbose,
|
|
125
|
+
presetConfig
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error(pc.red(`
|
|
130
|
+
Error: ${error.message}`));
|
|
131
|
+
if (options.verbose && error.cause) {
|
|
132
|
+
console.error(error.cause);
|
|
133
|
+
}
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
program
|
|
138
|
+
.command('completion')
|
|
139
|
+
.description('Generate shell completion script')
|
|
140
|
+
.action(() => {
|
|
141
|
+
const script = `
|
|
142
|
+
###-begin-rek-completion-###
|
|
143
|
+
#
|
|
144
|
+
# rek command completion script
|
|
145
|
+
#
|
|
146
|
+
# Installation: rek completion >> ~/.bashrc (or ~/.zshrc)
|
|
147
|
+
# Or, maybe: source <(rek completion)
|
|
148
|
+
#
|
|
149
|
+
|
|
150
|
+
_rek_completions()
|
|
151
|
+
{
|
|
152
|
+
local cur prev words cword
|
|
153
|
+
_init_completion -n : || return
|
|
154
|
+
|
|
155
|
+
local presets="${PRESET_NAMES.map(p => '@' + p).join(' ')}"
|
|
156
|
+
local methods="GET POST PUT DELETE PATCH HEAD OPTIONS"
|
|
157
|
+
local opts="-v --verbose -j --json -h --help -V --version"
|
|
158
|
+
|
|
159
|
+
if [[ \\$cur == -* ]] ; then
|
|
160
|
+
COMPREPLY=( $(compgen -W "\\$opts" -- \\$cur) )
|
|
161
|
+
return 0
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
if [[ \\$cur == @* ]] ; then
|
|
165
|
+
COMPREPLY=( $(compgen -W "\\$presets" -- \\$cur) )
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# If prev is a method, we likely want a URL next
|
|
170
|
+
# If prev is a preset, we might want a path (handled by generic completion)
|
|
171
|
+
|
|
172
|
+
# Suggest methods if it's the first argument (and not a preset/option)
|
|
173
|
+
if [[ \\$cword -eq 1 && ! \\$cur == -* && ! \\$cur == @* ]]; then
|
|
174
|
+
COMPREPLY=( $(compgen -W "\\$methods" -- \\$cur) )
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
return 0
|
|
178
|
+
}
|
|
179
|
+
complete -F _rek_completions rek
|
|
180
|
+
###-end-rek-completion-###
|
|
181
|
+
`;
|
|
182
|
+
console.log(script);
|
|
183
|
+
});
|
|
184
|
+
program
|
|
185
|
+
.command('shell')
|
|
186
|
+
.alias('interactive')
|
|
187
|
+
.alias('repl')
|
|
188
|
+
.description('Start the interactive Rek Shell')
|
|
189
|
+
.action(async () => {
|
|
190
|
+
const { RekShell } = await import('./tui/shell.js');
|
|
191
|
+
const shell = new RekShell();
|
|
192
|
+
shell.start();
|
|
193
|
+
});
|
|
194
|
+
const bench = program.command('bench').description('Performance benchmarking tools');
|
|
195
|
+
bench
|
|
196
|
+
.command('load')
|
|
197
|
+
.description('Run a load test with real-time dashboard')
|
|
198
|
+
.argument('[args...]', 'URL and options (users=10 duration=10s mode=throughput http2)')
|
|
199
|
+
.addHelpText('after', `
|
|
200
|
+
${pc.bold(pc.yellow('Options (key=value):'))}
|
|
201
|
+
${pc.green('users')} Number of concurrent users ${pc.gray('(default: 50)')}
|
|
202
|
+
${pc.green('duration')} Test duration in seconds ${pc.gray('(default: 300)')}
|
|
203
|
+
${pc.green('ramp')} Ramp-up time in seconds ${pc.gray('(default: 5)')}
|
|
204
|
+
${pc.green('mode')} Test mode ${pc.gray('(default: throughput)')}
|
|
205
|
+
${pc.gray('Values: throughput, stress, realistic')}
|
|
206
|
+
${pc.green('http2')} Force HTTP/2 ${pc.gray('(default: false)')}
|
|
207
|
+
|
|
208
|
+
${pc.bold(pc.yellow('Examples:'))}
|
|
209
|
+
${pc.green('$ rek bench load httpbin.org/get users=100 duration=60 ramp=10')}
|
|
210
|
+
${pc.green('$ rek bench load https://api.com/heavy mode=stress http2=true')}
|
|
211
|
+
`)
|
|
212
|
+
.action(async (args) => {
|
|
213
|
+
let url = '';
|
|
214
|
+
let users = 50;
|
|
215
|
+
let duration = 300;
|
|
216
|
+
let mode = 'throughput';
|
|
217
|
+
let http2 = false;
|
|
218
|
+
let rampUp = 5;
|
|
219
|
+
for (const arg of args) {
|
|
220
|
+
if (arg.includes('=')) {
|
|
221
|
+
const [key, val] = arg.split('=');
|
|
222
|
+
const k = key.toLowerCase();
|
|
223
|
+
if (k === 'users' || k === 'u')
|
|
224
|
+
users = parseInt(val);
|
|
225
|
+
else if (k === 'duration' || k === 'd' || k === 'time')
|
|
226
|
+
duration = parseInt(val);
|
|
227
|
+
else if (k === 'mode' || k === 'm')
|
|
228
|
+
mode = val;
|
|
229
|
+
else if (k === 'http2')
|
|
230
|
+
http2 = val === 'true';
|
|
231
|
+
else if (k === 'ramp' || k === 'rampup')
|
|
232
|
+
rampUp = parseInt(val);
|
|
233
|
+
}
|
|
234
|
+
else if (arg.toLowerCase() === 'http2') {
|
|
235
|
+
http2 = true;
|
|
236
|
+
}
|
|
237
|
+
else if (!url) {
|
|
238
|
+
url = arg;
|
|
239
|
+
if (!url.startsWith('http'))
|
|
240
|
+
url = `https://${url}`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!url) {
|
|
244
|
+
console.error(pc.red('Error: URL is required. Example: rek bench load httpbin.org users=50'));
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
const { startLoadDashboard } = await import('./tui/load-dashboard.js');
|
|
248
|
+
await startLoadDashboard({ url, users, duration, mode, http2, rampUp });
|
|
249
|
+
});
|
|
250
|
+
program.parse();
|
|
251
|
+
}
|
|
252
|
+
main().catch((error) => {
|
|
253
|
+
console.error('CLI Error:', error.message);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/cli/presets.ts"],"names":[],"mappings":"AAiBA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,OAsCzC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as presets from '../presets/index.js';
|
|
2
|
+
import pc from '../utils/colors.js';
|
|
3
|
+
const ENV_MAPPING = {
|
|
4
|
+
openai: ['OPENAI_API_KEY'],
|
|
5
|
+
anthropic: ['ANTHROPIC_API_KEY'],
|
|
6
|
+
github: ['GITHUB_TOKEN'],
|
|
7
|
+
gitlab: ['GITLAB_TOKEN'],
|
|
8
|
+
stripe: ['STRIPE_SECRET_KEY'],
|
|
9
|
+
discord: ['DISCORD_TOKEN'],
|
|
10
|
+
slack: ['SLACK_TOKEN'],
|
|
11
|
+
vercel: ['VERCEL_TOKEN'],
|
|
12
|
+
supabase: ['SUPABASE_URL', 'SUPABASE_KEY'],
|
|
13
|
+
};
|
|
14
|
+
export function resolvePreset(name) {
|
|
15
|
+
const presetFn = presets[name];
|
|
16
|
+
if (!presetFn) {
|
|
17
|
+
console.error(pc.red(`Error: Unknown preset '@${name}'`));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const requiredEnvs = ENV_MAPPING[name];
|
|
21
|
+
const options = {};
|
|
22
|
+
if (requiredEnvs) {
|
|
23
|
+
let missing = false;
|
|
24
|
+
for (const envVar of requiredEnvs) {
|
|
25
|
+
const value = process.env[envVar];
|
|
26
|
+
if (!value) {
|
|
27
|
+
console.error(pc.yellow(`Warning: Missing env variable ${envVar} for preset @${name}`));
|
|
28
|
+
missing = true;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const key = mapEnvToOption(name, envVar);
|
|
32
|
+
options[key] = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (missing) {
|
|
36
|
+
console.log(pc.gray(`Tip: export ${requiredEnvs.join('=... ')}=...`));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return presetFn(options);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(pc.red(`Error initializing preset @${name}: ${error.message}`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function mapEnvToOption(preset, env) {
|
|
48
|
+
if (env.endsWith('_KEY') || env.endsWith('_TOKEN'))
|
|
49
|
+
return 'apiKey';
|
|
50
|
+
if (env === 'GITHUB_TOKEN')
|
|
51
|
+
return 'token';
|
|
52
|
+
if (env === 'GITLAB_TOKEN')
|
|
53
|
+
return 'token';
|
|
54
|
+
if (env === 'DISCORD_TOKEN')
|
|
55
|
+
return 'token';
|
|
56
|
+
if (env === 'SLACK_TOKEN')
|
|
57
|
+
return 'token';
|
|
58
|
+
if (env === 'VERCEL_TOKEN')
|
|
59
|
+
return 'token';
|
|
60
|
+
if (env === 'STRIPE_SECRET_KEY')
|
|
61
|
+
return 'secretKey';
|
|
62
|
+
if (env === 'SUPABASE_URL')
|
|
63
|
+
return 'projectUrl';
|
|
64
|
+
if (env === 'SUPABASE_KEY')
|
|
65
|
+
return 'apiKey';
|
|
66
|
+
return 'apiKey';
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-chat.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/ai-chat.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAC;AAKrC,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,SAAS,EAAE,QAAQ,GAAE,MAAiB,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,iBAwHrH"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import pc from '../../utils/colors.js';
|
|
3
|
+
import { createAIClient } from '../../ai/client.js';
|
|
4
|
+
export async function startAIChat(rl, provider = 'openai', apiKey, model) {
|
|
5
|
+
console.clear();
|
|
6
|
+
console.log(pc.bold(pc.magenta(`🤖 Rek AI Chat (${provider})`)));
|
|
7
|
+
console.log(pc.gray('Type your message. Ctrl+C to exit.'));
|
|
8
|
+
const envKey = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY';
|
|
9
|
+
const key = apiKey || process.env[envKey];
|
|
10
|
+
if (!key) {
|
|
11
|
+
console.log(pc.yellow(`
|
|
12
|
+
Warning: No API Key found for ${provider}.`));
|
|
13
|
+
console.log(`Please set it via environment variable ${pc.bold(envKey)} or passing it to the command.`);
|
|
14
|
+
console.log(`Example: set ${envKey}=sk-... inside the shell.`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const client = createAIClient({
|
|
18
|
+
defaultProvider: provider,
|
|
19
|
+
providers: {
|
|
20
|
+
[provider]: { apiKey: key }
|
|
21
|
+
},
|
|
22
|
+
observability: false
|
|
23
|
+
});
|
|
24
|
+
const history = [
|
|
25
|
+
{ role: 'system', content: 'You are Recker AI, a helpful and concise assistant in a terminal environment.' }
|
|
26
|
+
];
|
|
27
|
+
rl.setPrompt(pc.magenta('You › '));
|
|
28
|
+
rl.prompt();
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const onLine = async (line) => {
|
|
31
|
+
const input = line.trim();
|
|
32
|
+
if (!input) {
|
|
33
|
+
rl.prompt();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (input.toLowerCase() === '/clear') {
|
|
37
|
+
history.length = 1;
|
|
38
|
+
console.clear();
|
|
39
|
+
console.log(pc.gray('Context cleared.'));
|
|
40
|
+
rl.prompt();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (input.toLowerCase() === '/exit') {
|
|
44
|
+
cleanup();
|
|
45
|
+
resolve();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
history.push({ role: 'user', content: input });
|
|
49
|
+
rl.pause();
|
|
50
|
+
process.stdout.write(pc.cyan('AI › '));
|
|
51
|
+
let fullResponse = '';
|
|
52
|
+
try {
|
|
53
|
+
const stream = await client.stream({
|
|
54
|
+
provider: provider,
|
|
55
|
+
model: model,
|
|
56
|
+
messages: history
|
|
57
|
+
});
|
|
58
|
+
for await (const chunk of stream) {
|
|
59
|
+
const content = typeof chunk === 'string' ? chunk : chunk.content || chunk.delta?.content || '';
|
|
60
|
+
if (content) {
|
|
61
|
+
process.stdout.write(content);
|
|
62
|
+
fullResponse += content;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
process.stdout.write('\n');
|
|
66
|
+
history.push({ role: 'assistant', content: fullResponse });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.log(pc.red(`
|
|
70
|
+
Error: ${error.message}`));
|
|
71
|
+
if (error.cause)
|
|
72
|
+
console.log(pc.gray(error.cause));
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
rl.resume();
|
|
76
|
+
rl.prompt();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const cleanup = () => {
|
|
80
|
+
rl.off('line', onLine);
|
|
81
|
+
rl.off('SIGINT', onSigInt);
|
|
82
|
+
process.stdin.off('keypress', onKeypress);
|
|
83
|
+
};
|
|
84
|
+
if (process.stdin.isTTY)
|
|
85
|
+
readline.emitKeypressEvents(process.stdin);
|
|
86
|
+
const onKeypress = (_str, key) => {
|
|
87
|
+
if (key && key.name === 'escape') {
|
|
88
|
+
cleanup();
|
|
89
|
+
resolve();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
process.stdin.on('keypress', onKeypress);
|
|
93
|
+
rl.on('line', onLine);
|
|
94
|
+
const onSigInt = () => {
|
|
95
|
+
cleanup();
|
|
96
|
+
resolve();
|
|
97
|
+
};
|
|
98
|
+
rl.once('SIGINT', onSigInt);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +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;AAKrE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,UAAU,iBA4E1D"}
|