@rulebricks/cli 2.0.0 → 2.0.2

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 (38) hide show
  1. package/README.md +3 -3
  2. package/benchmarks/README.md +98 -0
  3. package/benchmarks/Test Flow.rbf +4088 -0
  4. package/benchmarks/benchmark-flow.json +26 -0
  5. package/benchmarks/lib/payload.js +101 -0
  6. package/benchmarks/lib/report.js +929 -0
  7. package/benchmarks/qps-test.js +136 -0
  8. package/benchmarks/run-qps-test.sh +115 -0
  9. package/benchmarks/run-throughput-test.sh +123 -0
  10. package/benchmarks/throughput-report.html +632 -0
  11. package/benchmarks/throughput-results.json +298 -0
  12. package/benchmarks/throughput-test.js +159 -0
  13. package/dist/commands/benchmark.d.ts +11 -0
  14. package/dist/commands/benchmark.js +173 -0
  15. package/dist/commands/deploy.js +15 -4
  16. package/dist/commands/destroy.js +2 -2
  17. package/dist/commands/logs.js +1 -0
  18. package/dist/components/Wizard/steps/BenchmarkSteps.d.ts +31 -0
  19. package/dist/components/Wizard/steps/BenchmarkSteps.js +304 -0
  20. package/dist/components/Wizard/steps/DatabaseStep.js +49 -35
  21. package/dist/index.js +42 -6
  22. package/dist/lib/benchmark.d.ts +63 -0
  23. package/dist/lib/benchmark.js +466 -0
  24. package/dist/lib/dns.d.ts +3 -1
  25. package/dist/lib/dns.js +138 -56
  26. package/dist/lib/helm.d.ts +14 -1
  27. package/dist/lib/helm.js +36 -1
  28. package/dist/lib/kubernetes.js +2 -0
  29. package/dist/types/index.d.ts +90 -0
  30. package/dist/types/index.js +51 -0
  31. package/package.json +8 -6
  32. package/terraform/aws/main.tf +22 -0
  33. package/terraform/azure/main.tf +45 -0
  34. package/terraform/gcp/main.tf +34 -0
  35. /package/{email-templates → templates}/email_change.html +0 -0
  36. /package/{email-templates → templates}/invite.html +0 -0
  37. /package/{email-templates → templates}/password_change.html +0 -0
  38. /package/{email-templates → templates}/verify.html +0 -0
@@ -0,0 +1,298 @@
1
+ {
2
+ "root_group": {
3
+ "name": "",
4
+ "path": "",
5
+ "id": "d41d8cd98f00b204e9800998ecf8427e",
6
+ "groups": [],
7
+ "checks": [
8
+ {
9
+ "name": "status is 200",
10
+ "path": "::status is 200",
11
+ "id": "6210a8cd14cd70477eba5c5e4cb3fb5f",
12
+ "passes": 24000,
13
+ "fails": 0
14
+ },
15
+ {
16
+ "name": "valid response",
17
+ "path": "::valid response",
18
+ "id": "74e6940490a7b557672e61f02e36f4e8",
19
+ "passes": 24000,
20
+ "fails": 0
21
+ },
22
+ {
23
+ "name": "no error in response",
24
+ "path": "::no error in response",
25
+ "id": "04d657588d6c66446d4937ecba2292a2",
26
+ "passes": 24000,
27
+ "fails": 0
28
+ }
29
+ ]
30
+ },
31
+ "options": {
32
+ "summaryTrendStats": [
33
+ "avg",
34
+ "min",
35
+ "med",
36
+ "max",
37
+ "p(90)",
38
+ "p(95)"
39
+ ],
40
+ "summaryTimeUnit": "",
41
+ "noColor": false
42
+ },
43
+ "state": {
44
+ "isStdOutTTY": true,
45
+ "isStdErrTTY": true,
46
+ "testRunDurationMs": 300227.96
47
+ },
48
+ "metrics": {
49
+ "request_duration": {
50
+ "type": "trend",
51
+ "contains": "default",
52
+ "values": {
53
+ "p(95)": 318,
54
+ "avg": 207.51141666666666,
55
+ "min": 98,
56
+ "med": 195,
57
+ "max": 1305,
58
+ "p(90)": 285
59
+ }
60
+ },
61
+ "http_req_sending": {
62
+ "values": {
63
+ "p(90)": 0.443,
64
+ "p(95)": 0.575,
65
+ "avg": 0.3505282157261422,
66
+ "min": 0.029,
67
+ "med": 0.265,
68
+ "max": 197.681
69
+ },
70
+ "type": "trend",
71
+ "contains": "time"
72
+ },
73
+ "http_req_failed": {
74
+ "type": "rate",
75
+ "contains": "default",
76
+ "values": {
77
+ "rate": 0,
78
+ "passes": 0,
79
+ "fails": 30001
80
+ }
81
+ },
82
+ "checks": {
83
+ "type": "rate",
84
+ "contains": "default",
85
+ "values": {
86
+ "rate": 1,
87
+ "passes": 72000,
88
+ "fails": 0
89
+ }
90
+ },
91
+ "successes": {
92
+ "type": "rate",
93
+ "contains": "default",
94
+ "values": {
95
+ "rate": 1,
96
+ "passes": 24000,
97
+ "fails": 0
98
+ }
99
+ },
100
+ "http_req_blocked": {
101
+ "contains": "time",
102
+ "values": {
103
+ "med": 0.001,
104
+ "max": 446.733,
105
+ "p(90)": 0.001,
106
+ "p(95)": 0.002,
107
+ "avg": 1.6850438318703274,
108
+ "min": 0
109
+ },
110
+ "type": "trend"
111
+ },
112
+ "http_req_receiving": {
113
+ "type": "trend",
114
+ "contains": "time",
115
+ "values": {
116
+ "avg": 2.110890670310989,
117
+ "min": 0.019,
118
+ "med": 0.203,
119
+ "max": 206.634,
120
+ "p(90)": 3.99,
121
+ "p(95)": 5.81
122
+ }
123
+ },
124
+ "http_req_duration": {
125
+ "values": {
126
+ "avg": 204.29118289390323,
127
+ "min": 97.82,
128
+ "med": 193.159,
129
+ "max": 990.433,
130
+ "p(90)": 283.411,
131
+ "p(95)": 316.351
132
+ },
133
+ "type": "trend",
134
+ "contains": "time"
135
+ },
136
+ "vus_max": {
137
+ "type": "gauge",
138
+ "contains": "default",
139
+ "values": {
140
+ "value": 200,
141
+ "min": 200,
142
+ "max": 200
143
+ }
144
+ },
145
+ "errors{phase:measurement}": {
146
+ "values": {
147
+ "rate": 0,
148
+ "passes": 0,
149
+ "fails": 24000
150
+ },
151
+ "thresholds": {
152
+ "rate<0.05": {
153
+ "ok": true
154
+ }
155
+ },
156
+ "type": "rate",
157
+ "contains": "default"
158
+ },
159
+ "http_reqs": {
160
+ "contains": "default",
161
+ "values": {
162
+ "count": 30001,
163
+ "rate": 99.9274018315949
164
+ },
165
+ "type": "counter"
166
+ },
167
+ "data_received": {
168
+ "type": "counter",
169
+ "contains": "data",
170
+ "values": {
171
+ "count": 409778192,
172
+ "rate": 1364890.172121211
173
+ }
174
+ },
175
+ "iterations": {
176
+ "type": "counter",
177
+ "contains": "default",
178
+ "values": {
179
+ "count": 30001,
180
+ "rate": 99.9274018315949
181
+ }
182
+ },
183
+ "data_sent": {
184
+ "type": "counter",
185
+ "contains": "data",
186
+ "values": {
187
+ "count": 104185619,
188
+ "rate": 347021.7064393336
189
+ }
190
+ },
191
+ "http_req_tls_handshaking": {
192
+ "type": "trend",
193
+ "contains": "time",
194
+ "values": {
195
+ "avg": 0.9106618112729572,
196
+ "min": 0,
197
+ "med": 0,
198
+ "max": 306.889,
199
+ "p(90)": 0,
200
+ "p(95)": 0
201
+ }
202
+ },
203
+ "http_req_waiting": {
204
+ "contains": "time",
205
+ "values": {
206
+ "p(90)": 280.835,
207
+ "p(95)": 313.283,
208
+ "avg": 201.82976400786623,
209
+ "min": 94.379,
210
+ "med": 190.745,
211
+ "max": 933.167
212
+ },
213
+ "type": "trend"
214
+ },
215
+ "iteration_duration": {
216
+ "type": "trend",
217
+ "contains": "time",
218
+ "values": {
219
+ "p(95)": 324.982625,
220
+ "avg": 209.38244802586436,
221
+ "min": 100.226958,
222
+ "med": 196.689375,
223
+ "max": 1306.521125,
224
+ "p(90)": 288.073125
225
+ }
226
+ },
227
+ "errors": {
228
+ "type": "rate",
229
+ "contains": "default",
230
+ "values": {
231
+ "rate": 0,
232
+ "passes": 0,
233
+ "fails": 24000
234
+ }
235
+ },
236
+ "http_req_duration{phase:measurement}": {
237
+ "type": "trend",
238
+ "contains": "time",
239
+ "values": {
240
+ "p(95)": 316.0782,
241
+ "avg": 205.77155337500048,
242
+ "min": 97.82,
243
+ "med": 194.721,
244
+ "max": 990.433,
245
+ "p(90)": 283.5602
246
+ },
247
+ "thresholds": {
248
+ "p(95)<2000": {
249
+ "ok": true
250
+ },
251
+ "p(99)<5000": {
252
+ "ok": true
253
+ }
254
+ }
255
+ },
256
+ "http_req_connecting": {
257
+ "type": "trend",
258
+ "contains": "time",
259
+ "values": {
260
+ "avg": 0.7645731142295258,
261
+ "min": 0,
262
+ "med": 0,
263
+ "max": 257.371,
264
+ "p(90)": 0,
265
+ "p(95)": 0
266
+ }
267
+ },
268
+ "http_req_duration{expected_response:true}": {
269
+ "contains": "time",
270
+ "values": {
271
+ "avg": 204.29118289390323,
272
+ "min": 97.82,
273
+ "med": 193.159,
274
+ "max": 990.433,
275
+ "p(90)": 283.411,
276
+ "p(95)": 316.351
277
+ },
278
+ "type": "trend"
279
+ },
280
+ "vus": {
281
+ "type": "gauge",
282
+ "contains": "default",
283
+ "values": {
284
+ "max": 71,
285
+ "value": 21,
286
+ "min": 13
287
+ }
288
+ },
289
+ "total_payloads": {
290
+ "contains": "default",
291
+ "values": {
292
+ "count": 1200000,
293
+ "rate": 3996.9628411690906
294
+ },
295
+ "type": "counter"
296
+ }
297
+ }
298
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Throughput (Solutions Per Second) Benchmark Test
3
+ *
4
+ * Measures rule engine capacity by sending bulk payload requests at a constant rate.
5
+ * This test helps you understand:
6
+ * - How many rule evaluations your deployment can process per second
7
+ * - Maximum throughput for batch workloads
8
+ * - Engine performance under sustained load
9
+ *
10
+ * Test Structure:
11
+ * - 1 minute warm-up phase (allows cluster to scale, excluded from results)
12
+ * - 4 minutes measurement phase (steady-state performance)
13
+ *
14
+ * Usage:
15
+ * k6 run -e API_URL=https://your-instance.com/api/v1/flows/flow_id \
16
+ * -e API_KEY=your-api-key \
17
+ * throughput-test.js
18
+ *
19
+ * Optional environment variables:
20
+ * TEST_DURATION - Measurement duration after warm-up (default: 4m)
21
+ * TARGET_RPS - Target bulk requests per second (default: 100)
22
+ * BULK_SIZE - Number of payloads per request (default: 50)
23
+ */
24
+
25
+ import { check } from "k6";
26
+ import http from "k6/http";
27
+ import { Counter, Rate, Trend } from "k6/metrics";
28
+ import {
29
+ generateBulkPayload,
30
+ getConfig,
31
+ createRequestParams,
32
+ } from "./lib/payload.js";
33
+ import {
34
+ generateThroughputReport,
35
+ generateThroughputConsoleSummary,
36
+ } from "./lib/report.js";
37
+
38
+ // Load configuration
39
+ const config = getConfig({
40
+ testDuration: "4m",
41
+ targetRps: 100,
42
+ bulkSize: 50,
43
+ });
44
+
45
+ // Custom metrics (only for measurement phase)
46
+ const errorRate = new Rate("errors");
47
+ const successRate = new Rate("successes");
48
+ const requestDuration = new Trend("request_duration");
49
+ const droppedRequests = new Counter("dropped_requests");
50
+ const totalPayloads = new Counter("total_payloads");
51
+ const failedPayloads = new Counter("failed_payloads");
52
+
53
+ // k6 options with warm-up and measurement phases
54
+ export const options = {
55
+ scenarios: {
56
+ // Warm-up phase: 1 minute to let cluster scale
57
+ warm_up: {
58
+ executor: "constant-arrival-rate",
59
+ rate: config.targetRps,
60
+ timeUnit: "1s",
61
+ duration: "1m",
62
+ preAllocatedVUs: Math.min(config.targetRps, 100),
63
+ maxVUs: Math.min(config.targetRps * 3, 500),
64
+ exec: "warmUp",
65
+ tags: { phase: "warmup" },
66
+ },
67
+ // Measurement phase: actual test after warm-up
68
+ throughput_test: {
69
+ executor: "constant-arrival-rate",
70
+ rate: config.targetRps,
71
+ timeUnit: "1s",
72
+ duration: config.testDuration,
73
+ preAllocatedVUs: Math.min(config.targetRps, 100),
74
+ maxVUs: Math.min(config.targetRps * 3, 500),
75
+ startTime: "1m", // Start after warm-up
76
+ exec: "measureTest",
77
+ tags: { phase: "measurement" },
78
+ },
79
+ },
80
+ thresholds: {
81
+ // Only apply thresholds to measurement phase
82
+ "http_req_duration{phase:measurement}": ["p(95)<2000", "p(99)<5000"],
83
+ "errors{phase:measurement}": ["rate<0.05"],
84
+ },
85
+ };
86
+
87
+ // Request parameters (longer timeout for bulk requests)
88
+ const params = createRequestParams(config.apiKey, "30s");
89
+
90
+ /**
91
+ * Warm-up function - same as test but metrics tagged differently
92
+ */
93
+ export function warmUp() {
94
+ const bulkPayload = generateBulkPayload(config.bulkSize);
95
+
96
+ try {
97
+ http.post(config.apiUrl, JSON.stringify(bulkPayload), params);
98
+ } catch (error) {
99
+ // Ignore errors during warm-up
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Measurement test function - sends bulk requests
105
+ */
106
+ export function measureTest() {
107
+ const bulkPayload = generateBulkPayload(config.bulkSize);
108
+ const start = Date.now();
109
+
110
+ try {
111
+ const response = http.post(
112
+ config.apiUrl,
113
+ JSON.stringify(bulkPayload),
114
+ params
115
+ );
116
+
117
+ const duration = Date.now() - start;
118
+ requestDuration.add(duration);
119
+
120
+ const success = check(response, {
121
+ "status is 200": (r) => r.status === 200,
122
+ "valid response": (r) => r.body && r.body.length > 0,
123
+ "no error in response": (r) => {
124
+ try {
125
+ const body = JSON.parse(r.body);
126
+ return !body.error;
127
+ } catch (e) {
128
+ return false;
129
+ }
130
+ },
131
+ });
132
+
133
+ errorRate.add(!success);
134
+ successRate.add(success);
135
+ totalPayloads.add(config.bulkSize);
136
+
137
+ if (!success) {
138
+ droppedRequests.add(1);
139
+ failedPayloads.add(config.bulkSize);
140
+ }
141
+ } catch (error) {
142
+ errorRate.add(1);
143
+ successRate.add(0);
144
+ droppedRequests.add(1);
145
+ totalPayloads.add(config.bulkSize);
146
+ failedPayloads.add(config.bulkSize);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Generate summary report
152
+ */
153
+ export function handleSummary(data) {
154
+ return {
155
+ stdout: generateThroughputConsoleSummary(data, config),
156
+ "throughput-report.html": generateThroughputReport(data, config),
157
+ "throughput-results.json": JSON.stringify(data, null, 2),
158
+ };
159
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Benchmark Command
3
+ *
4
+ * Interactive wizard for configuring and running k6 load tests
5
+ * against Rulebricks deployments.
6
+ */
7
+ interface BenchmarkCommandProps {
8
+ name?: string;
9
+ }
10
+ export declare function BenchmarkCommand(props: BenchmarkCommandProps): import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -0,0 +1,173 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Benchmark Command
4
+ *
5
+ * Interactive wizard for configuring and running k6 load tests
6
+ * against Rulebricks deployments.
7
+ */
8
+ import { useState, useEffect, useCallback } from "react";
9
+ import { Box, Text, useApp, useInput } from "ink";
10
+ import { BorderBox, Spinner, ThemeProvider, useTheme, Logo, } from "../components/common/index.js";
11
+ import { DeploymentSelectStep, ApiKeyStep, FlowSlugStep, TestModeStep, PresetsStep, ReviewStep, createInitialBenchmarkState, } from "../components/Wizard/steps/BenchmarkSteps.js";
12
+ import { isK6Installed, getK6InstallInstructions, runBenchmark, buildApiUrl, openInBrowser, formatDuration, } from "../lib/benchmark.js";
13
+ function BenchmarkCommandInner({ name }) {
14
+ const { exit } = useApp();
15
+ const { colors } = useTheme();
16
+ const [step, setStep] = useState("preflight");
17
+ const [wizardState, setWizardState] = useState(createInitialBenchmarkState());
18
+ const [error, setError] = useState(null);
19
+ const [k6Output, setK6Output] = useState([]);
20
+ const [result, setResult] = useState(null);
21
+ // Preflight check for k6
22
+ useEffect(() => {
23
+ if (step !== "preflight")
24
+ return;
25
+ (async () => {
26
+ const installed = await isK6Installed();
27
+ if (!installed) {
28
+ setError(`k6 is not installed.\n\n${getK6InstallInstructions()}\n\nVisit https://k6.io/docs/get-started/installation/ for more options.`);
29
+ setStep("error");
30
+ return;
31
+ }
32
+ // If a deployment name was provided via CLI, skip the selection step
33
+ if (name) {
34
+ setWizardState((s) => ({ ...s, deploymentName: name }));
35
+ // We still need to load the deployment URL, which the DeploymentSelectStep does
36
+ // So we'll go to that step anyway to validate and load the URL
37
+ }
38
+ setStep("select-deployment");
39
+ })();
40
+ }, [step, name]);
41
+ // Handle wizard step completion
42
+ const handleStepComplete = useCallback((data) => {
43
+ setWizardState((s) => ({ ...s, ...data }));
44
+ // Progress to next step
45
+ switch (step) {
46
+ case "select-deployment":
47
+ setStep("api-key");
48
+ break;
49
+ case "api-key":
50
+ setStep("flow-slug");
51
+ break;
52
+ case "flow-slug":
53
+ setStep("test-mode");
54
+ break;
55
+ case "test-mode":
56
+ setStep("presets");
57
+ break;
58
+ case "presets":
59
+ setStep("review");
60
+ break;
61
+ case "review":
62
+ setStep("running");
63
+ break;
64
+ }
65
+ }, [step]);
66
+ // Handle going back
67
+ const handleBack = useCallback(() => {
68
+ switch (step) {
69
+ case "select-deployment":
70
+ exit();
71
+ break;
72
+ case "api-key":
73
+ setStep("select-deployment");
74
+ break;
75
+ case "flow-slug":
76
+ setStep("api-key");
77
+ break;
78
+ case "test-mode":
79
+ setStep("flow-slug");
80
+ break;
81
+ case "presets":
82
+ setStep("test-mode");
83
+ break;
84
+ case "review":
85
+ setStep("presets");
86
+ break;
87
+ }
88
+ }, [step, exit]);
89
+ // Run the benchmark
90
+ useEffect(() => {
91
+ if (step !== "running")
92
+ return;
93
+ (async () => {
94
+ const config = {
95
+ deploymentName: wizardState.deploymentName,
96
+ apiUrl: buildApiUrl(wizardState.deploymentUrl, wizardState.flowSlug),
97
+ apiKey: wizardState.apiKey,
98
+ testMode: wizardState.testMode,
99
+ testDuration: wizardState.testDuration,
100
+ targetRps: wizardState.targetRps,
101
+ bulkSize: wizardState.testMode === "throughput"
102
+ ? wizardState.bulkSize
103
+ : undefined,
104
+ };
105
+ const benchmarkResult = await runBenchmark(config, {
106
+ onOutput: (line) => {
107
+ setK6Output((prev) => {
108
+ // Keep only last 15 lines to avoid memory issues
109
+ const newOutput = [...prev, line];
110
+ if (newOutput.length > 15) {
111
+ return newOutput.slice(-15);
112
+ }
113
+ return newOutput;
114
+ });
115
+ },
116
+ });
117
+ setResult(benchmarkResult);
118
+ if (benchmarkResult.success) {
119
+ setStep("complete");
120
+ // Try to open report in browser
121
+ try {
122
+ await openInBrowser(benchmarkResult.reportPath);
123
+ }
124
+ catch {
125
+ // Ignore browser open errors
126
+ }
127
+ }
128
+ else {
129
+ setError(benchmarkResult.error || "Benchmark failed");
130
+ setStep("error");
131
+ }
132
+ })();
133
+ }, [step, wizardState]);
134
+ // Handle key input for error/complete screens
135
+ useInput((input, key) => {
136
+ if (key.escape && (step === "error" || step === "complete")) {
137
+ exit();
138
+ }
139
+ });
140
+ // Render preflight check
141
+ if (step === "preflight") {
142
+ return (_jsx(BorderBox, { title: "Benchmark", children: _jsx(Box, { marginY: 1, children: _jsx(Spinner, { label: "Checking prerequisites..." }) }) }));
143
+ }
144
+ // Render error screen
145
+ if (step === "error") {
146
+ return (_jsx(BorderBox, { title: "Benchmark Failed", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, bold: true, children: "Error" }), _jsx(Text, { color: colors.error, children: error }), result?.outputDir && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Output directory: ", result.outputDir] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Press Esc to exit" }) })] }) }));
147
+ }
148
+ // Render running screen
149
+ if (step === "running") {
150
+ const expectedThroughput = wizardState.testMode === "throughput"
151
+ ? wizardState.targetRps * wizardState.bulkSize
152
+ : wizardState.targetRps;
153
+ return (_jsx(BorderBox, { title: "Running Benchmark", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { label: `Running ${wizardState.testMode.toUpperCase()} test against ${wizardState.deploymentName}...` }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: colors.muted, children: ["Target: ", expectedThroughput.toLocaleString(), " ", wizardState.testMode === "throughput" ? "solutions" : "requests", "/sec"] }), _jsxs(Text, { color: colors.muted, children: ["Duration: 1m warm-up + ", formatDuration(wizardState.testDuration)] })] }), k6Output.length > 0 && (_jsx(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: k6Output.map((line, i) => (_jsx(Text, { color: colors.muted, dimColor: true, children: line.length > 80 ? line.slice(0, 77) + "..." : line }, i))) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, dimColor: true, children: "This may take several minutes. Please wait..." }) })] }) }));
154
+ }
155
+ // Render complete screen
156
+ if (step === "complete" && result) {
157
+ const metrics = result.metrics;
158
+ return (_jsx(BorderBox, { title: "Benchmark Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.success, bold: true, children: "Benchmark completed successfully!" }), metrics && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Results Summary:" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Success Rate: " }), _jsxs(Text, { color: metrics.successRate >= 99
159
+ ? colors.success
160
+ : metrics.successRate >= 95
161
+ ? colors.warning
162
+ : colors.error, bold: true, children: [metrics.successRate.toFixed(1), "%"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Actual RPS: " }), _jsx(Text, { color: colors.accent, bold: true, children: metrics.actualRps.toFixed(1) })] }), metrics.actualThroughput && (_jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Throughput: " }), _jsxs(Text, { color: colors.accent, bold: true, children: [metrics.actualThroughput.toFixed(0), " solutions/sec"] })] })), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "P95 Latency: " }), _jsxs(Text, { color: metrics.p95Latency < 200
163
+ ? colors.success
164
+ : metrics.p95Latency < 500
165
+ ? colors.warning
166
+ : colors.error, children: [metrics.p95Latency.toFixed(0), "ms"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "P99 Latency: " }), _jsxs(Text, { children: [metrics.p99Latency.toFixed(0), "ms"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Total Requests: " }), _jsx(Text, { children: metrics.totalRequests.toLocaleString() })] })] })] })), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: colors.muted, children: "Results saved to:" }), _jsx(Text, { color: colors.accent, children: result.outputDir })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, children: ["Report: ", result.reportPath.split("/").pop()] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.success, children: "The HTML report should open in your browser automatically." }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Press Esc to exit" }) })] }) }));
167
+ }
168
+ // Render wizard steps
169
+ return (_jsxs(_Fragment, { children: [step === "select-deployment" && (_jsx(DeploymentSelectStep, { onComplete: handleStepComplete, onBack: handleBack, state: wizardState })), step === "api-key" && (_jsx(ApiKeyStep, { onComplete: handleStepComplete, onBack: handleBack, state: wizardState })), step === "flow-slug" && (_jsx(FlowSlugStep, { onComplete: handleStepComplete, onBack: handleBack, state: wizardState })), step === "test-mode" && (_jsx(TestModeStep, { onComplete: handleStepComplete, onBack: handleBack, state: wizardState })), step === "presets" && (_jsx(PresetsStep, { onComplete: handleStepComplete, onBack: handleBack, state: wizardState })), step === "review" && (_jsx(ReviewStep, { onComplete: handleStepComplete, onBack: handleBack, state: wizardState }))] }));
170
+ }
171
+ export function BenchmarkCommand(props) {
172
+ return (_jsxs(ThemeProvider, { theme: "status", children: [_jsx(Logo, {}), _jsx(BenchmarkCommandInner, { ...props })] }));
173
+ }