@ganakailabs/cloudeval-cli 0.18.4

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.
@@ -0,0 +1,12 @@
1
+ import {
2
+ Banner,
3
+ bannerSegmentColor,
4
+ splitBannerLineSegments
5
+ } from "./chunk-GY4W535V.js";
6
+ import "./chunk-RQ2GBK43.js";
7
+ import "./chunk-UOCT7M4J.js";
8
+ export {
9
+ Banner,
10
+ bannerSegmentColor,
11
+ splitBannerLineSegments
12
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ Onboarding
3
+ } from "./chunk-3DVPEIVB.js";
4
+ import "./chunk-4QIKW5TJ.js";
5
+ import "./chunk-UOCT7M4J.js";
6
+ export {
7
+ Onboarding
8
+ };
@@ -0,0 +1,229 @@
1
+ import {
2
+ terminalTheme
3
+ } from "./chunk-UOCT7M4J.js";
4
+
5
+ // src/reports/ReportDashboard.tsx
6
+ import { useMemo, useState } from "react";
7
+ import { Box, Text, useApp, useInput } from "ink";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ var modeOrder = [
10
+ "overview",
11
+ "formatted",
12
+ "parsed",
13
+ "raw"
14
+ ];
15
+ var isCostReport = (report) => report.kind === "cost";
16
+ var isWafReport = (report) => report.kind === "waf";
17
+ var formatCurrency = (amount, currency = "USD") => {
18
+ const prefix = currency === "USD" ? "$" : `${currency} `;
19
+ return amount >= 1e3 ? `${prefix}${(amount / 1e3).toFixed(1)}k` : `${prefix}${amount}`;
20
+ };
21
+ var truncate = (value, width) => value.length > width ? `${value.slice(0, Math.max(0, width - 3))}...` : value;
22
+ var statusColor = (status) => {
23
+ if (status === "pass" || status === "ok") return terminalTheme.success;
24
+ if (status === "fail" || status === "over") return terminalTheme.danger;
25
+ return terminalTheme.warning;
26
+ };
27
+ var Panel = ({ title, label, children }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: terminalTheme.muted, paddingX: 1, children: [
28
+ /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
29
+ /* @__PURE__ */ jsx(Text, { bold: true, children: title }),
30
+ label ? /* @__PURE__ */ jsx(Text, { color: terminalTheme.accent, children: label }) : null
31
+ ] }),
32
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children })
33
+ ] });
34
+ var Metric = ({
35
+ label,
36
+ value,
37
+ note,
38
+ color
39
+ }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: terminalTheme.muted, paddingX: 1, minWidth: 22, children: [
40
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: label }),
41
+ /* @__PURE__ */ jsx(Text, { bold: true, color, children: value }),
42
+ note ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: note }) : null
43
+ ] });
44
+ var Bar = ({
45
+ value,
46
+ max = 100,
47
+ color,
48
+ width = 24
49
+ }) => {
50
+ const filled = Math.max(0, Math.min(width, Math.round(value / max * width)));
51
+ return /* @__PURE__ */ jsxs(Text, { color, children: [
52
+ "\u2588".repeat(filled),
53
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2591".repeat(width - filled) })
54
+ ] });
55
+ };
56
+ var JsonBlock = ({ value }) => /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: JSON.stringify(value, null, 2) });
57
+ var FormattedBlock = ({ report }) => {
58
+ const formatted = report.formatted;
59
+ if (!formatted) {
60
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No formatted report returned. Use parsed/raw view." });
61
+ }
62
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
63
+ /* @__PURE__ */ jsx(Text, { bold: true, children: formatted.title }),
64
+ /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: formatted.summary }),
65
+ formatted.sections.map((section) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
66
+ /* @__PURE__ */ jsx(Text, { bold: true, color: terminalTheme.brand, children: section.title }),
67
+ /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: section.markdown })
68
+ ] }, section.id))
69
+ ] });
70
+ };
71
+ var CostOverview = ({
72
+ report
73
+ }) => {
74
+ const parsed = report.parsed;
75
+ const maxService = Math.max(...parsed.serviceGroups.map((group) => group.amount), 1);
76
+ const compact = (process.stdout.columns || 100) < 110;
77
+ const chartWidth = compact ? 16 : 24;
78
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
79
+ /* @__PURE__ */ jsxs(Box, { gap: 1, flexWrap: "wrap", children: [
80
+ /* @__PURE__ */ jsx(
81
+ Metric,
82
+ {
83
+ label: "Monthly spend",
84
+ value: formatCurrency(parsed.totalSpend.amount, parsed.totalSpend.currency),
85
+ note: `${parsed.totalSpend.changePercent?.toFixed(1) ?? "0"}% vs previous`,
86
+ color: terminalTheme.warning
87
+ }
88
+ ),
89
+ /* @__PURE__ */ jsx(
90
+ Metric,
91
+ {
92
+ label: "Estimated savings",
93
+ value: formatCurrency(parsed.estimatedSavings.amount, parsed.estimatedSavings.currency),
94
+ note: `${parsed.estimatedSavings.percentOfSpend?.toFixed(1) ?? "0"}% addressable`,
95
+ color: terminalTheme.success
96
+ }
97
+ ),
98
+ /* @__PURE__ */ jsx(
99
+ Metric,
100
+ {
101
+ label: "Anomalies",
102
+ value: String(parsed.anomalies.length),
103
+ note: "spend spikes",
104
+ color: terminalTheme.warning
105
+ }
106
+ )
107
+ ] }),
108
+ /* @__PURE__ */ jsxs(Box, { gap: 1, flexDirection: compact ? "column" : "row", children: [
109
+ /* @__PURE__ */ jsx(Panel, { title: "Spend mix", label: "top services", children: parsed.serviceGroups.slice(0, 6).map((group) => /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
110
+ /* @__PURE__ */ jsx(Text, { children: truncate(group.name, 12).padEnd(12) }),
111
+ /* @__PURE__ */ jsx(Bar, { value: group.amount, max: maxService, color: terminalTheme.brand, width: chartWidth }),
112
+ /* @__PURE__ */ jsx(Text, { children: formatCurrency(group.amount, group.currency) })
113
+ ] }, group.name)) }),
114
+ /* @__PURE__ */ jsx(Panel, { title: "Savings recommendations", label: `${formatCurrency(parsed.estimatedSavings.amount)}/mo`, children: parsed.recommendations.slice(0, 5).map((item, index) => /* @__PURE__ */ jsxs(Text, { wrap: "wrap", children: [
115
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
116
+ String(index + 1).padStart(2, "0"),
117
+ ". "
118
+ ] }),
119
+ item.title,
120
+ " ",
121
+ /* @__PURE__ */ jsxs(Text, { color: terminalTheme.success, children: [
122
+ formatCurrency(item.monthlySavings, item.currency),
123
+ "/mo"
124
+ ] }),
125
+ " ",
126
+ /* @__PURE__ */ jsxs(Text, { color: statusColor(item.risk), children: [
127
+ "risk ",
128
+ item.risk
129
+ ] })
130
+ ] }, item.id)) })
131
+ ] }),
132
+ /* @__PURE__ */ jsx(Panel, { title: "Budgets", label: "usage", children: parsed.budgets.map((budget) => /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
133
+ /* @__PURE__ */ jsx(Text, { children: budget.name.padEnd(8) }),
134
+ /* @__PURE__ */ jsx(Bar, { value: budget.usedPercent, max: 110, color: statusColor(budget.status) }),
135
+ /* @__PURE__ */ jsxs(Text, { color: statusColor(budget.status), children: [
136
+ budget.usedPercent,
137
+ "%"
138
+ ] })
139
+ ] }, budget.name)) })
140
+ ] });
141
+ };
142
+ var WafOverview = ({
143
+ report
144
+ }) => {
145
+ const parsed = report.parsed;
146
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
147
+ /* @__PURE__ */ jsxs(Box, { gap: 1, flexWrap: "wrap", children: [
148
+ /* @__PURE__ */ jsx(Metric, { label: "WAF score", value: `${parsed.score.overall}/100`, color: terminalTheme.warning }),
149
+ /* @__PURE__ */ jsx(Metric, { label: "Passed controls", value: String(parsed.counts.passed), color: terminalTheme.success }),
150
+ /* @__PURE__ */ jsx(Metric, { label: "High risk", value: String(parsed.counts.highRisk), color: terminalTheme.danger }),
151
+ /* @__PURE__ */ jsx(Metric, { label: "Evidence coverage", value: `${parsed.counts.evidenceCoveragePercent}%`, color: terminalTheme.brand })
152
+ ] }),
153
+ /* @__PURE__ */ jsx(Panel, { title: "Pillar distribution", label: "score", children: parsed.score.pillars.map((pillar) => /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
154
+ /* @__PURE__ */ jsx(Text, { children: truncate(pillar.label, 12).padEnd(12) }),
155
+ /* @__PURE__ */ jsx(Bar, { value: pillar.score, color: statusColor(pillar.score < 70 ? "fail" : pillar.score < 80 ? "warn" : "pass"), width: 30 }),
156
+ /* @__PURE__ */ jsx(Text, { children: String(pillar.score).padStart(3) }),
157
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
158
+ pillar.passed,
159
+ " pass / ",
160
+ pillar.warned,
161
+ " warn / ",
162
+ pillar.failed,
163
+ " fail"
164
+ ] })
165
+ ] }, pillar.id)) }),
166
+ /* @__PURE__ */ jsx(Panel, { title: "Rule matrix", label: "priority", children: parsed.rules.slice(0, 8).map((rule) => /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
167
+ /* @__PURE__ */ jsx(Text, { children: rule.id.padEnd(8) }),
168
+ /* @__PURE__ */ jsx(Text, { color: statusColor(rule.status), children: rule.status.padEnd(4) }),
169
+ /* @__PURE__ */ jsx(Text, { children: truncate(rule.pillar, 12).padEnd(12) }),
170
+ /* @__PURE__ */ jsx(Text, { children: truncate(rule.title, 52) })
171
+ ] }, rule.id)) })
172
+ ] });
173
+ };
174
+ var ReportDashboard = ({
175
+ report,
176
+ initialMode = "overview"
177
+ }) => {
178
+ const { exit } = useApp();
179
+ const [mode, setMode] = useState(initialMode);
180
+ const modeIndex = modeOrder.indexOf(mode);
181
+ const generated = useMemo(() => new Date(report.generatedAt).toISOString(), [report.generatedAt]);
182
+ useInput((input, key) => {
183
+ if (input === "q" || key.escape) {
184
+ exit();
185
+ return;
186
+ }
187
+ if (input === "1") setMode("overview");
188
+ if (input === "2") setMode("formatted");
189
+ if (input === "3") setMode("parsed");
190
+ if (input === "4") setMode("raw");
191
+ if (key.leftArrow || key.rightArrow || key.tab) {
192
+ const direction = key.leftArrow ? -1 : 1;
193
+ setMode(modeOrder[(modeIndex + direction + modeOrder.length) % modeOrder.length]);
194
+ }
195
+ });
196
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, gap: 1, children: [
197
+ /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
198
+ /* @__PURE__ */ jsxs(Text, { color: terminalTheme.success, children: [
199
+ "CloudEval ",
200
+ report.kind.toUpperCase(),
201
+ " report"
202
+ ] }),
203
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: report.id })
204
+ ] }),
205
+ /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
206
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
207
+ "Project ",
208
+ report.projectId,
209
+ " | Generated ",
210
+ generated,
211
+ " | Source ",
212
+ report.source.provider
213
+ ] }),
214
+ /* @__PURE__ */ jsx(Text, { children: "[1] overview [2] formatted [3] parsed [4] raw | q quit" })
215
+ ] }),
216
+ /* @__PURE__ */ jsxs(Text, { color: terminalTheme.brand, children: [
217
+ "View: ",
218
+ mode
219
+ ] }),
220
+ mode === "overview" && isCostReport(report) ? /* @__PURE__ */ jsx(CostOverview, { report }) : null,
221
+ mode === "overview" && isWafReport(report) ? /* @__PURE__ */ jsx(WafOverview, { report }) : null,
222
+ mode === "formatted" ? /* @__PURE__ */ jsx(Panel, { title: "Formatted report", label: "human", children: /* @__PURE__ */ jsx(FormattedBlock, { report }) }) : null,
223
+ mode === "parsed" ? /* @__PURE__ */ jsx(Panel, { title: "Parsed report", label: "normalized", children: /* @__PURE__ */ jsx(JsonBlock, { value: report.parsed }) }) : null,
224
+ mode === "raw" ? /* @__PURE__ */ jsx(Panel, { title: "Raw report", label: "provider", children: /* @__PURE__ */ jsx(JsonBlock, { value: report.raw }) }) : null
225
+ ] });
226
+ };
227
+ export {
228
+ ReportDashboard
229
+ };
@@ -0,0 +1,185 @@
1
+ import {
2
+ completeOnboarding
3
+ } from "./chunk-4QIKW5TJ.js";
4
+ import {
5
+ terminalTheme
6
+ } from "./chunk-UOCT7M4J.js";
7
+
8
+ // src/ui/components/Onboarding.tsx
9
+ import { useState } from "react";
10
+ import { Box, Text, useInput } from "ink";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ var ROLES = [
13
+ "Developer",
14
+ "DevOps Engineer",
15
+ "Cloud Architect",
16
+ "Platform Engineer",
17
+ "SRE",
18
+ "Other"
19
+ ];
20
+ var TEAM_SIZES = ["1", "2-5", "6-10", "11-50", "51-200", "200+"];
21
+ var GOALS = [
22
+ "Infrastructure as Code",
23
+ "Cloud Migration",
24
+ "Cost Optimization",
25
+ "Security & Compliance",
26
+ "Automation",
27
+ "Multi-cloud Strategy"
28
+ ];
29
+ var CLOUD_PROVIDERS = ["Azure", "AWS", "GCP", "Multi-cloud", "Other"];
30
+ var Onboarding = ({
31
+ baseUrl,
32
+ token,
33
+ onComplete
34
+ }) => {
35
+ const [step, setStep] = useState(0);
36
+ const [data, setData] = useState({
37
+ name: "",
38
+ role: "",
39
+ teamSize: "",
40
+ goals: [],
41
+ cloudProvider: ""
42
+ });
43
+ const [selectedIndex, setSelectedIndex] = useState(0);
44
+ const [error, setError] = useState(null);
45
+ const [submitting, setSubmitting] = useState(false);
46
+ useInput((input, key) => {
47
+ if (submitting) return;
48
+ if (key.upArrow) {
49
+ setSelectedIndex((i) => Math.max(0, i - 1));
50
+ } else if (key.downArrow) {
51
+ if (step === 3) {
52
+ setSelectedIndex((i) => Math.min(GOALS.length - 1, i + 1));
53
+ } else if (step === 1) {
54
+ setSelectedIndex((i) => Math.min(ROLES.length - 1, i + 1));
55
+ } else if (step === 2) {
56
+ setSelectedIndex((i) => Math.min(TEAM_SIZES.length - 1, i + 1));
57
+ } else if (step === 4) {
58
+ setSelectedIndex((i) => Math.min(CLOUD_PROVIDERS.length - 1, i + 1));
59
+ }
60
+ } else if (input === " " && step === 3) {
61
+ const goal = GOALS[selectedIndex];
62
+ setData((prev) => ({
63
+ ...prev,
64
+ goals: prev.goals.includes(goal) ? prev.goals.filter((g) => g !== goal) : [...prev.goals, goal]
65
+ }));
66
+ } else if (key.return) {
67
+ if (step === 0) {
68
+ if (data.name.trim()) {
69
+ setStep(1);
70
+ setSelectedIndex(0);
71
+ }
72
+ } else if (step === 1) {
73
+ setData((prev) => ({ ...prev, role: ROLES[selectedIndex] }));
74
+ setStep(2);
75
+ setSelectedIndex(0);
76
+ } else if (step === 2) {
77
+ setData((prev) => ({ ...prev, teamSize: TEAM_SIZES[selectedIndex] }));
78
+ setStep(3);
79
+ setSelectedIndex(0);
80
+ } else if (step === 3) {
81
+ if (data.goals.length > 0) {
82
+ setStep(4);
83
+ setSelectedIndex(0);
84
+ }
85
+ } else if (step === 4) {
86
+ const nextData = {
87
+ ...data,
88
+ cloudProvider: CLOUD_PROVIDERS[selectedIndex]
89
+ };
90
+ setData(nextData);
91
+ handleSubmit(nextData);
92
+ }
93
+ } else if (key.backspace && step === 0) {
94
+ setData((prev) => ({ ...prev, name: prev.name.slice(0, -1) }));
95
+ } else if (step === 0 && input.length === 1 && !key.ctrl && !key.meta) {
96
+ setData((prev) => ({ ...prev, name: prev.name + input }));
97
+ }
98
+ });
99
+ const handleSubmit = async (submission = data) => {
100
+ setSubmitting(true);
101
+ setError(null);
102
+ try {
103
+ await completeOnboarding(baseUrl, token, {
104
+ name: submission.name,
105
+ role: submission.role,
106
+ teamSize: submission.teamSize,
107
+ goals: submission.goals,
108
+ cloudProvider: submission.cloudProvider
109
+ });
110
+ onComplete();
111
+ } catch (err) {
112
+ setError(err.message || "Failed to complete onboarding");
113
+ setSubmitting(false);
114
+ }
115
+ };
116
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, gap: 1, children: [
117
+ /* @__PURE__ */ jsx(Text, { bold: true, color: terminalTheme.brand, children: "Welcome to Cloudeval CLI! Let's get you set up." }),
118
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
119
+ "Step ",
120
+ step + 1,
121
+ " of 5"
122
+ ] }),
123
+ step === 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
124
+ /* @__PURE__ */ jsx(Text, { children: "What's your name?" }),
125
+ /* @__PURE__ */ jsxs(Text, { children: [
126
+ /* @__PURE__ */ jsx(Text, { color: terminalTheme.success, children: data.name }),
127
+ /* @__PURE__ */ jsx(Text, { color: terminalTheme.cursor, children: "\u258C" })
128
+ ] }),
129
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type your name and press Enter" })
130
+ ] }),
131
+ step === 1 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
132
+ /* @__PURE__ */ jsx(Text, { children: "What's your role?" }),
133
+ ROLES.map((role, idx) => /* @__PURE__ */ jsxs(Text, { color: idx === selectedIndex ? terminalTheme.brand : void 0, children: [
134
+ idx === selectedIndex ? "> " : " ",
135
+ role
136
+ ] }, role)),
137
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate, Enter to select" })
138
+ ] }),
139
+ step === 2 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
140
+ /* @__PURE__ */ jsx(Text, { children: "What's your team size?" }),
141
+ TEAM_SIZES.map((size, idx) => /* @__PURE__ */ jsxs(Text, { color: idx === selectedIndex ? terminalTheme.brand : void 0, children: [
142
+ idx === selectedIndex ? "> " : " ",
143
+ size
144
+ ] }, size)),
145
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate, Enter to select" })
146
+ ] }),
147
+ step === 3 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
148
+ /* @__PURE__ */ jsx(Text, { children: "What are your goals? (Select multiple with Space)" }),
149
+ GOALS.map((goal, idx) => /* @__PURE__ */ jsxs(Text, { color: idx === selectedIndex ? terminalTheme.brand : void 0, children: [
150
+ data.goals.includes(goal) ? "\u2713 " : " ",
151
+ idx === selectedIndex ? "> " : " ",
152
+ goal
153
+ ] }, goal)),
154
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
155
+ "Selected: ",
156
+ data.goals.length,
157
+ " goal(s). Press Enter to continue."
158
+ ] })
159
+ ] }),
160
+ step === 4 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
161
+ /* @__PURE__ */ jsx(Text, { children: "Which cloud provider do you use?" }),
162
+ CLOUD_PROVIDERS.map((provider, idx) => /* @__PURE__ */ jsxs(
163
+ Text,
164
+ {
165
+ color: idx === selectedIndex ? terminalTheme.brand : void 0,
166
+ children: [
167
+ idx === selectedIndex ? "> " : " ",
168
+ provider
169
+ ]
170
+ },
171
+ provider
172
+ )),
173
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate, Enter to select" })
174
+ ] }),
175
+ error && /* @__PURE__ */ jsxs(Text, { color: terminalTheme.danger, children: [
176
+ "Error: ",
177
+ error
178
+ ] }),
179
+ submitting && /* @__PURE__ */ jsx(Text, { color: terminalTheme.warning, children: "Completing onboarding..." })
180
+ ] });
181
+ };
182
+
183
+ export {
184
+ Onboarding
185
+ };