@ganakailabs/cloudeval-cli 0.30.1 → 0.30.3
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 +204 -135
- package/THIRD_PARTY_NOTICES.md +2 -1
- package/dist/{App-IRVY4LSK.js → App-SKVX7NAF.js} +2 -2
- package/dist/{Banner-4PWF5O3B.js → Banner-CRBHEOTC.js} +2 -2
- package/dist/{chunk-STE265DV.js → chunk-FPZWMNAI.js} +1 -1
- package/dist/{chunk-P5JXFRYX.js → chunk-QB3BBKVH.js} +1 -1
- package/dist/cli.js +351 -39
- package/package.json +15 -11
- package/sbom.spdx.json +17 -1
package/README.md
CHANGED
|
@@ -1,197 +1,266 @@
|
|
|
1
1
|
# CloudEval CLI
|
|
2
2
|
|
|
3
|
+
Review Azure infrastructure before merge - from CLI, CI, and AI agents.
|
|
4
|
+
|
|
3
5
|
<p align="center">
|
|
4
6
|
<img src="https://raw.githubusercontent.com/ganakailabs/cloudeval-cli/main/docs/assets/images/cli/tui-chat.png" alt="CloudEval CLI terminal UI" width="100%">
|
|
5
7
|
</p>
|
|
6
8
|
|
|
7
9
|
<p align="center">
|
|
8
10
|
<a href="https://www.npmjs.com/package/@ganakailabs/cloudeval-cli"><img alt="npm version" src="https://img.shields.io/npm/v/@ganakailabs/cloudeval-cli?style=flat-square&logo=npm"></a>
|
|
9
|
-
<a href="https://www.npmjs.com/package/@ganakailabs/cloudeval-cli"><img alt="npm downloads" src="https://img.shields.io/npm/dm/@ganakailabs/cloudeval-cli?style=flat-square&logo=npm&label=npm%20downloads"></a>
|
|
10
|
-
<a href="https://github.com/ganakailabs/cloudeval-cli/releases"><img alt="release" src="https://img.shields.io/github/v/release/ganakailabs/cloudeval-cli?sort=semver&style=flat-square"></a>
|
|
11
|
-
<a href="https://github.com/ganakailabs/cloudeval-cli/releases"><img alt="GitHub downloads" src="https://img.shields.io/github/downloads/ganakailabs/cloudeval-cli/total?style=flat-square&logo=github&label=release%20downloads"></a>
|
|
12
11
|
<a href="https://github.com/ganakailabs/cloudeval-cli/actions/workflows/semantic-release.yml"><img alt="release health" src="https://img.shields.io/github/actions/workflow/status/ganakailabs/cloudeval-cli/semantic-release.yml?branch=main&style=flat-square&label=release%20health"></a>
|
|
13
|
-
<a href="https://
|
|
12
|
+
<a href="https://cloudeval.ai"><img alt="CloudEval app" src="https://img.shields.io/badge/app-cloudeval.ai-b6f23c?style=flat-square&labelColor=0b0f0a"></a>
|
|
14
13
|
<a href="https://docs.cloudeval.ai/reference/cli-overview"><img alt="docs" src="https://img.shields.io/badge/docs-docs.cloudeval.ai-2d6cdf?style=flat-square"></a>
|
|
15
|
-
<a href="https://
|
|
14
|
+
<a href="https://github.com/ganakailabs/cloudeval-cli/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/badge/license-CloudEval%20CLI%20License-blue?style=flat-square"></a>
|
|
16
15
|
</p>
|
|
17
16
|
|
|
18
|
-
CloudEval CLI brings CloudEval into
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
CloudEval CLI brings **CloudEval** into terminals, CI pipelines, and MCP-capable coding agents. Use it to review Azure ARM templates, Bicep-generated ARM JSON, and live Azure context with cost, architecture, and Well-Architected signals.
|
|
18
|
+
|
|
19
|
+
## What It Does
|
|
20
|
+
|
|
21
|
+
CloudEval helps teams catch infrastructure risk before merge:
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
- reviews **ARM JSON** and **Bicep-generated ARM JSON** templates;
|
|
24
|
+
- validates templates from local files or CI workspaces;
|
|
25
|
+
- connects to live Azure context for cloud review workflows;
|
|
26
|
+
- exposes machine-readable output for scripts and GitHub Actions;
|
|
27
|
+
- runs as an MCP server for Codex, Cursor, Claude, VS Code, and other clients.
|
|
24
28
|
|
|
25
|
-
##
|
|
29
|
+
## Quickstart: Run Your First Azure/IaC Review
|
|
30
|
+
|
|
31
|
+
Install from npm:
|
|
26
32
|
|
|
27
33
|
```bash
|
|
28
34
|
npm install -g @ganakailabs/cloudeval-cli
|
|
29
35
|
cloudeval --help
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
Sign in for local use:
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
```bash
|
|
41
|
+
cloudeval login
|
|
42
|
+
cloudeval status
|
|
43
|
+
```
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
the installers:
|
|
45
|
+
Validate an ARM template:
|
|
40
46
|
|
|
41
47
|
```bash
|
|
42
|
-
|
|
48
|
+
cloudeval validate template \
|
|
49
|
+
--template-file ./infra/azuredeploy.json \
|
|
50
|
+
--wait \
|
|
51
|
+
--progress stderr \
|
|
52
|
+
--format json \
|
|
53
|
+
--non-interactive
|
|
43
54
|
```
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
irm https://cli.cloudeval.ai/install.ps1 | iex
|
|
47
|
-
```
|
|
56
|
+
Full setup docs: [Use the CLI](https://docs.cloudeval.ai/quickstart/use-the-cli.md) and [CLI command reference](https://docs.cloudeval.ai/reference/cli-command-reference.md).
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
skip clients where CloudEval MCP is already configured, and offer setup only
|
|
51
|
-
for missing clients that can be configured automatically. Manual-only clients
|
|
52
|
-
are summarized with a follow-up command instead of forcing another prompt.
|
|
53
|
-
It also asks whether to share limited CLI telemetry, defaulting to yes; decline
|
|
54
|
-
or set `CLOUDEVAL_TELEMETRY=0` to write `telemetry.enabled=false`.
|
|
55
|
-
When a CLI update exposes new MCP capabilities, restart or reload your MCP
|
|
56
|
-
client when you are ready; CloudEval never restarts those apps automatically.
|
|
58
|
+
## Choose Your Workflow
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
use `cloudeval login` for first-run auth, `cloudeval mcp setup <client>` to
|
|
60
|
-
configure MCP in your editor, or `cloudeval update` (TTY) to rerun the shell
|
|
61
|
-
installer and optionally accept agent/MCP setup prompts.
|
|
60
|
+
### Local ARM / Bicep-Generated ARM JSON
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
Use `validate template` for local review and scriptable checks:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cloudeval validate template \
|
|
66
|
+
--template-file ./infra/azuredeploy.json \
|
|
67
|
+
--parameters-file ./infra/azuredeploy.parameters.json \
|
|
68
|
+
--wait \
|
|
69
|
+
--progress stderr \
|
|
70
|
+
--format json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Live Azure Sync
|
|
74
|
+
|
|
75
|
+
Use CloudEval projects and reports after connecting Azure in the app or CLI:
|
|
64
76
|
|
|
65
77
|
```bash
|
|
66
|
-
cloudeval # Terminal UI
|
|
67
|
-
cloudeval login # Browser device login
|
|
68
|
-
cloudeval status # Account, API, and local CLI status
|
|
69
|
-
cloudeval ask "Summarize my cloud risk" --format json
|
|
70
|
-
cloudeval agent "Find cost and architecture risks" --format json
|
|
71
|
-
cloudeval agents list
|
|
72
|
-
cloudeval agents run cost --project <project-id> --format json
|
|
73
78
|
cloudeval projects list
|
|
74
79
|
cloudeval reports list
|
|
80
|
+
cloudeval ask "Summarize my Azure architecture risks" --format json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### GitHub Actions / CI
|
|
84
|
+
|
|
85
|
+
Use a scoped `CLOUDEVAL_ACCESS_KEY` secret and keep generated JSON on stdout:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
name: CloudEval review
|
|
89
|
+
|
|
90
|
+
on:
|
|
91
|
+
pull_request:
|
|
92
|
+
paths:
|
|
93
|
+
- "infra/**"
|
|
94
|
+
|
|
95
|
+
jobs:
|
|
96
|
+
cloudeval:
|
|
97
|
+
runs-on: ubuntu-latest
|
|
98
|
+
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
|
|
102
|
+
- uses: actions/setup-node@v4
|
|
103
|
+
with:
|
|
104
|
+
node-version: "20"
|
|
105
|
+
|
|
106
|
+
- run: npm install -g @ganakailabs/cloudeval-cli
|
|
107
|
+
|
|
108
|
+
- name: Validate ARM template
|
|
109
|
+
env:
|
|
110
|
+
CLOUDEVAL_ACCESS_KEY: ${{ secrets.CLOUDEVAL_ACCESS_KEY }}
|
|
111
|
+
run: |
|
|
112
|
+
cloudeval validate template \
|
|
113
|
+
--template-file ./infra/azuredeploy.json \
|
|
114
|
+
--wait \
|
|
115
|
+
--progress stderr \
|
|
116
|
+
--format json \
|
|
117
|
+
--non-interactive
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Public example: [passing baseline PR #6](https://github.com/ganakailabs/cloudeval-azure-arm-review-example/pull/6) in [`ganakailabs/cloudeval-azure-arm-review-example`](https://github.com/ganakailabs/cloudeval-azure-arm-review-example). Review comments show a merge-gate table, CloudEval report badges, a visible AI summary, a folded detailed AI reviewer note, a Well-Architected radar/table drilldown, and cost Mermaid charts.
|
|
121
|
+
|
|
122
|
+
### MCP For Codex, Cursor, Claude, VS Code
|
|
123
|
+
|
|
124
|
+
Start with read-only agent integration:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
75
127
|
cloudeval mcp serve --toolset readonly
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Setup docs: [MCP client setup](https://docs.cloudeval.ai/reference/mcp-client-setup.md) and [agent automation rules](https://docs.cloudeval.ai/reference/agent-and-automation-rules.md).
|
|
131
|
+
|
|
132
|
+
## Example Outputs
|
|
133
|
+
|
|
134
|
+
Human-facing commands print concise summaries by default:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
cloudeval status
|
|
138
|
+
cloudeval reports list
|
|
139
|
+
cloudeval rules search "public network"
|
|
140
|
+
cloudeval agents run cost --project <project-id> --format json
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Automation should request structured output:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
76
146
|
cloudeval capabilities --format json
|
|
147
|
+
cloudeval validate template --template-file ./infra/azuredeploy.json --wait --format json --non-interactive
|
|
148
|
+
cloudeval ask "Summarize top risks" --format ndjson --progress ndjson --non-interactive
|
|
77
149
|
```
|
|
78
150
|
|
|
79
|
-
##
|
|
151
|
+
## Trust, Privacy, And Limits
|
|
152
|
+
|
|
153
|
+
CloudEval is designed for review workflows, not silent cloud mutation.
|
|
154
|
+
|
|
155
|
+
- Azure is the primary supported live-cloud provider today.
|
|
156
|
+
- ARM JSON and Bicep-generated ARM JSON are the strongest current IaC paths.
|
|
157
|
+
- AWS and GCP live sync are not full-parity workflows today.
|
|
158
|
+
- Machine-readable commands write payloads to stdout.
|
|
159
|
+
- Prompts, warnings, progress, and browser-open messages go to stderr.
|
|
160
|
+
- Telemetry does not send raw prompts, command output, tokens, local paths, resource IDs, tenant IDs, cloud resource names, stack traces, or raw error messages.
|
|
161
|
+
- Use `--format json --non-interactive` for scripts and CI.
|
|
162
|
+
- Use `cloudeval mcp serve --toolset readonly` as the default agent integration mode.
|
|
163
|
+
|
|
164
|
+
Privacy and automation details: [agent and automation rules](https://docs.cloudeval.ai/reference/agent-and-automation-rules.md).
|
|
80
165
|
|
|
81
|
-
|
|
82
|
-
events only. CloudEval records command family, success, duration, safe option
|
|
83
|
-
enums, CLI/runtime versions, OS major version, architecture, install source,
|
|
84
|
-
update/install outcomes, MCP tool names, TUI metadata, a W3C `traceId`, and a
|
|
85
|
-
per-command `requestId` for backend correlation. After login, events may include
|
|
86
|
-
a hashed internal user ID only.
|
|
166
|
+
## Automation Contract
|
|
87
167
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
168
|
+
CloudEval separates machine output from human/operator messages:
|
|
169
|
+
|
|
170
|
+
- **stdout**: JSON, NDJSON, Markdown, or text payload requested by `--format`;
|
|
171
|
+
- **stderr**: prompts, warnings, progress, browser-open messages, and MCP diagnostics;
|
|
172
|
+
- **JSON/NDJSON**: use `--format json` for one final payload or `--format ndjson` for streaming events where supported;
|
|
173
|
+
- **exit codes**: non-zero exits indicate failed commands, validation failures, missing auth, or required human approval;
|
|
174
|
+
- **non-interactive mode**: use `--non-interactive` in CI so commands fail instead of prompting.
|
|
175
|
+
|
|
176
|
+
Recommended CI shape:
|
|
92
177
|
|
|
93
178
|
```bash
|
|
94
|
-
cloudeval
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
179
|
+
cloudeval validate template \
|
|
180
|
+
--template-file ./infra/azuredeploy.json \
|
|
181
|
+
--wait \
|
|
182
|
+
--progress stderr \
|
|
183
|
+
--format json \
|
|
184
|
+
--non-interactive
|
|
98
185
|
```
|
|
99
186
|
|
|
100
|
-
|
|
187
|
+
## MCP For Coding Agents
|
|
188
|
+
|
|
189
|
+
Use MCP when an AI coding agent should inspect CloudEval projects, reports, rules, recipes, or validation capabilities.
|
|
101
190
|
|
|
102
|
-
|
|
191
|
+
```bash
|
|
192
|
+
cloudeval mcp serve --toolset readonly
|
|
193
|
+
```
|
|
103
194
|
|
|
104
|
-
|
|
105
|
-
sessions, and auth:
|
|
195
|
+
Common setup commands:
|
|
106
196
|
|
|
107
197
|
```bash
|
|
108
|
-
cloudeval
|
|
198
|
+
codex mcp add cloudeval -- cloudeval mcp serve --toolset readonly
|
|
199
|
+
cloudeval mcp setup cursor --dry-run --toolset readonly --format json
|
|
200
|
+
cloudeval mcp setup vscode --dry-run --toolset readonly --format json
|
|
109
201
|
```
|
|
110
202
|
|
|
111
|
-
|
|
203
|
+
MCP stdout is reserved for JSON-RPC. Diagnostics go to stderr.
|
|
204
|
+
|
|
205
|
+
## Advanced Install, Update, Uninstall
|
|
206
|
+
|
|
207
|
+
Standalone installers are available for macOS, Linux, WSL2, Git Bash, and PowerShell 7+:
|
|
112
208
|
|
|
113
209
|
```bash
|
|
114
|
-
cloudeval
|
|
210
|
+
curl -fsSL https://cli.cloudeval.ai/install.sh | bash
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```powershell
|
|
214
|
+
irm https://cli.cloudeval.ai/install.ps1 | iex
|
|
115
215
|
```
|
|
116
216
|
|
|
117
|
-
|
|
217
|
+
Update and uninstall:
|
|
118
218
|
|
|
119
219
|
```bash
|
|
220
|
+
cloudeval update --check
|
|
221
|
+
cloudeval update --yes
|
|
222
|
+
cloudeval uninstall --dry-run
|
|
223
|
+
cloudeval uninstall --yes
|
|
120
224
|
cloudeval uninstall --yes --remove-config
|
|
225
|
+
npm uninstall -g @ganakailabs/cloudeval-cli
|
|
121
226
|
```
|
|
122
227
|
|
|
123
|
-
|
|
228
|
+
The installer can offer optional MCP setup for detected clients. It does not create access keys or write secrets into MCP client config.
|
|
229
|
+
|
|
230
|
+
## Full Docs
|
|
231
|
+
|
|
232
|
+
- [CloudEval app](https://cloudeval.ai)
|
|
233
|
+
- [CLI overview](https://docs.cloudeval.ai/reference/cli-overview)
|
|
234
|
+
- [Use the CLI](https://docs.cloudeval.ai/quickstart/use-the-cli.md)
|
|
235
|
+
- [CLI command reference](https://docs.cloudeval.ai/reference/cli-command-reference.md)
|
|
236
|
+
- [MCP client setup](https://docs.cloudeval.ai/reference/mcp-client-setup.md)
|
|
237
|
+
- [Agent and automation rules](https://docs.cloudeval.ai/reference/agent-and-automation-rules.md)
|
|
238
|
+
- [Sign-in and onboarding troubleshooting](https://docs.cloudeval.ai/troubleshooting/sign-in-and-onboarding.md)
|
|
239
|
+
- [GitHub issues](https://github.com/ganakailabs/cloudeval-cli/issues)
|
|
240
|
+
- [Releases](https://github.com/ganakailabs/cloudeval-cli/releases)
|
|
241
|
+
- [Discord](https://discord.gg/tk5dcU2a7T)
|
|
242
|
+
|
|
243
|
+
## Build From Source / Contributing
|
|
244
|
+
|
|
245
|
+
Read [AGENTS.md](https://github.com/ganakailabs/cloudeval-cli/blob/main/AGENTS.md) before touching auth, credentials, smoke artifacts, or user-facing command behavior.
|
|
124
246
|
|
|
125
247
|
```bash
|
|
126
|
-
|
|
248
|
+
git clone https://github.com/ganakailabs/cloudeval-cli.git
|
|
249
|
+
cd cloudeval-cli
|
|
250
|
+
pnpm install
|
|
251
|
+
pnpm build
|
|
252
|
+
pnpm -C packages/cli dev --help
|
|
127
253
|
```
|
|
128
254
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
sessions, recent CloudEval chat threads, and local CLI sessions. `/thread new`
|
|
142
|
-
starts another independent open session, and `/open` opens the matching
|
|
143
|
-
CloudEval web chat thread when the active session has a thread id. Roomy
|
|
144
|
-
terminals show a context rail with project, thread, model, mode, profile, report
|
|
145
|
-
artifact chips; narrower terminals
|
|
146
|
-
keep the chat first and expose the same controls through the docked composer and
|
|
147
|
-
slash commands. Typing `/` opens a bottom command completion strip; use Tab or
|
|
148
|
-
Up/Down to move, Right to accept the ghost text, and Enter to choose the
|
|
149
|
-
highlighted command. Streaming work appears as a task ledger in the thread.
|
|
150
|
-
Grounded answers show numbered citations and a Sources section instead of raw
|
|
151
|
-
`[S_tool_...]` tags, with citation numbers highlighted inline; `/copy` copies
|
|
152
|
-
the latest assistant response and `/download` writes a Markdown transcript with
|
|
153
|
-
the same references. Project and Connection tabs include selected-item detail
|
|
154
|
-
panes for backend fields, report coverage, sync state, and linked records; use
|
|
155
|
-
`J`/`K` or Up/Down on Projects and Connections to move the selected row, then
|
|
156
|
-
Enter to confirm it. The billing header separates credits left from observed
|
|
157
|
-
credits used so usage does not look like the current budget. Use the Profile
|
|
158
|
-
control or `/profile architecture|cost|triage|remediation` to choose an Agent
|
|
159
|
-
Profile for the next chat stream. Selecting a profile switches to Agent mode;
|
|
160
|
-
selecting Ask mode clears the profile back to the default chat flow. Starter
|
|
161
|
-
prompts stay hidden until you run `/starter`. Press `Esc` from the prompt to
|
|
162
|
-
leave text editing so tab, arrow, and number shortcuts move through controls
|
|
163
|
-
and tabs; type again to resume editing. Busy loaders and the input cursor animate
|
|
164
|
-
unless you pass `--no-anim`. The
|
|
165
|
-
banner details include the logged-in user. Focused controls and the active top
|
|
166
|
-
tab use the shared warm banner-yellow accent, with the active tab filled across
|
|
167
|
-
its full button interior.
|
|
168
|
-
|
|
169
|
-
## Authentication
|
|
170
|
-
|
|
171
|
-
Use `cloudeval login` for local development. For CI or hosted agents, create a
|
|
172
|
-
scoped CloudEval access key in the app or with `cloudeval credentials create`,
|
|
173
|
-
then provide it as `CLOUDEVAL_ACCESS_KEY`.
|
|
174
|
-
|
|
175
|
-
Stored device-login sessions are refreshed automatically before authenticated
|
|
176
|
-
requests. If a long-running terminal session receives an expired-token response
|
|
177
|
-
from the chat stream, the CLI refreshes the stored session and retries the
|
|
178
|
-
request once. If the refresh token is revoked or expired, run `cloudeval login`
|
|
179
|
-
again.
|
|
180
|
-
|
|
181
|
-
## Documentation
|
|
182
|
-
|
|
183
|
-
- CLI overview: <https://docs.cloudeval.ai/reference/cli-overview>
|
|
184
|
-
- Command reference: <https://docs.cloudeval.ai/reference/cli-command-reference>
|
|
185
|
-
- MCP setup: <https://docs.cloudeval.ai/reference/mcp-client-setup>
|
|
186
|
-
- Release smoke tests: <https://github.com/ganakailabs/cloudeval-cli/blob/main/docs/release-smoke-tests.md>
|
|
187
|
-
|
|
188
|
-
## License And Notices
|
|
189
|
-
|
|
190
|
-
CloudEval CLI first-party code is provided under the
|
|
191
|
-
[CloudEval CLI License](https://github.com/ganakailabs/cloudeval-cli/blob/main/LICENSE).
|
|
192
|
-
Production third-party package attribution is published in
|
|
193
|
-
[THIRD_PARTY_NOTICES.md](https://github.com/ganakailabs/cloudeval-cli/blob/main/THIRD_PARTY_NOTICES.md),
|
|
194
|
-
and the release SBOM is published as
|
|
195
|
-
[sbom.spdx.json](https://github.com/ganakailabs/cloudeval-cli/blob/main/sbom.spdx.json).
|
|
196
|
-
Installer releases also place these notice files under
|
|
197
|
-
`~/.local/share/cloudeval/licenses`.
|
|
255
|
+
Run focused package checks:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
pnpm test:npm-package
|
|
259
|
+
pnpm -C packages/cli test:cli:noninteractive
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
CloudEval CLI first-party code is provided under the [CloudEval CLI License](https://github.com/ganakailabs/cloudeval-cli/blob/main/LICENSE).
|
|
265
|
+
|
|
266
|
+
Production third-party attribution is published in [THIRD_PARTY_NOTICES.md](https://github.com/ganakailabs/cloudeval-cli/blob/main/THIRD_PARTY_NOTICES.md), and the release SBOM is published as [sbom.spdx.json](https://github.com/ganakailabs/cloudeval-cli/blob/main/sbom.spdx.json).
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -14,7 +14,7 @@ This notice is not a substitute for legal review before public or enterprise dis
|
|
|
14
14
|
| --- | ---: |
|
|
15
15
|
| (MIT OR CC0-1.0) | 2 |
|
|
16
16
|
| 0BSD | 1 |
|
|
17
|
-
| Apache-2.0 |
|
|
17
|
+
| Apache-2.0 | 51 |
|
|
18
18
|
| BSD-3-Clause | 3 |
|
|
19
19
|
| ISC | 12 |
|
|
20
20
|
| MIT | 172 |
|
|
@@ -226,6 +226,7 @@ This notice is not a substitute for legal review before public or enterprise dis
|
|
|
226
226
|
| semver | 7.7.3 | ISC | GitHub Inc. | https://github.com/npm/node-semver#readme |
|
|
227
227
|
| shell-quote | 1.8.4 | MIT | James Halliday | https://github.com/ljharb/shell-quote |
|
|
228
228
|
| signal-exit | 3.0.7 | ISC | Ben Coe | https://github.com/tapjs/signal-exit |
|
|
229
|
+
| signalstory | 0.1.0 | Apache-2.0 | NOASSERTION | NOASSERTION |
|
|
229
230
|
| skin-tone | 2.0.0 | MIT | Sindre Sorhus | https://github.com/sindresorhus/skin-tone#readme |
|
|
230
231
|
| slice-ansi | 5.0.0 | MIT | NOASSERTION | https://github.com/chalk/slice-ansi#readme |
|
|
231
232
|
| slice-ansi | 6.0.0 | MIT | NOASSERTION | https://github.com/chalk/slice-ansi#readme |
|
|
@@ -38,10 +38,10 @@ import {
|
|
|
38
38
|
} from "./chunk-NXM4JEOB.js";
|
|
39
39
|
import {
|
|
40
40
|
Banner
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-QB3BBKVH.js";
|
|
42
42
|
import {
|
|
43
43
|
CLI_VERSION
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-FPZWMNAI.js";
|
|
45
45
|
import {
|
|
46
46
|
raisedButtonStyle,
|
|
47
47
|
terminalTheme
|
package/dist/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
} from "./chunk-NXM4JEOB.js";
|
|
40
40
|
import {
|
|
41
41
|
CLI_VERSION
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-FPZWMNAI.js";
|
|
43
43
|
|
|
44
44
|
// src/runtime/prepareInk.ts
|
|
45
45
|
import fs from "fs";
|
|
@@ -2541,6 +2541,85 @@ var fetchCloudEvalJson = async ({
|
|
|
2541
2541
|
return await response.json();
|
|
2542
2542
|
};
|
|
2543
2543
|
|
|
2544
|
+
// src/signalstoryReviewAdapter.ts
|
|
2545
|
+
import {
|
|
2546
|
+
createSignalStoryEngine,
|
|
2547
|
+
renderPlainText
|
|
2548
|
+
} from "signalstory/core";
|
|
2549
|
+
import { renderGithubSummary } from "signalstory/markdown";
|
|
2550
|
+
var renderSignalStoryPlainText = (parts = []) => renderPlainText(parts);
|
|
2551
|
+
var REVIEW_FALLBACK_RULE_PACK = {
|
|
2552
|
+
id: "cloudeval-review-fallback",
|
|
2553
|
+
rules: [
|
|
2554
|
+
{
|
|
2555
|
+
id: "review-fallback",
|
|
2556
|
+
when: { signal: "gateStatus", exists: true },
|
|
2557
|
+
story: {
|
|
2558
|
+
id: "review-fallback",
|
|
2559
|
+
severity: "high",
|
|
2560
|
+
icon: "git-pull-request",
|
|
2561
|
+
priority: 100,
|
|
2562
|
+
sentence: [
|
|
2563
|
+
{ text: "CloudEval review completed with " },
|
|
2564
|
+
{ path: "gateStatus", marks: ["bold"] },
|
|
2565
|
+
{ text: ". Well-Architected posture is " },
|
|
2566
|
+
{ path: "score", marks: ["bold"] },
|
|
2567
|
+
{ text: " (" },
|
|
2568
|
+
{ path: "rating", marks: ["bold"] },
|
|
2569
|
+
{ text: "), validation has " },
|
|
2570
|
+
{ path: "failedTests", suffix: " failed unit tests", marks: ["bold"] },
|
|
2571
|
+
{ text: ", policy checks are " },
|
|
2572
|
+
{ path: "policyStatus", marks: ["bold"] },
|
|
2573
|
+
{ text: ", and monthly cost is " },
|
|
2574
|
+
{ path: "monthlyCost", marks: ["bold"] },
|
|
2575
|
+
{ text: ". Prioritize " },
|
|
2576
|
+
{ text: "failed validation checks", marks: ["bold"] },
|
|
2577
|
+
{ text: " and " },
|
|
2578
|
+
{ path: "weakestPillar", marks: ["bold"] },
|
|
2579
|
+
{ text: " first." }
|
|
2580
|
+
],
|
|
2581
|
+
rationale: [
|
|
2582
|
+
{
|
|
2583
|
+
text: "Failed validation, weak architecture pillars, and cost over budget are the highest-signal remediation inputs before merge."
|
|
2584
|
+
}
|
|
2585
|
+
],
|
|
2586
|
+
action: { label: "Fix failed validation checks and rerun the review." }
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
]
|
|
2590
|
+
};
|
|
2591
|
+
var buildSignalStoryReviewFallback = (input) => {
|
|
2592
|
+
const engine = createSignalStoryEngine({
|
|
2593
|
+
rulePacks: [REVIEW_FALLBACK_RULE_PACK]
|
|
2594
|
+
});
|
|
2595
|
+
const stories = engine.generate({ signals: input });
|
|
2596
|
+
const primary = stories[0];
|
|
2597
|
+
if (!primary) {
|
|
2598
|
+
return null;
|
|
2599
|
+
}
|
|
2600
|
+
const shortSummary = renderSignalStoryPlainText(primary.sentence);
|
|
2601
|
+
const detailsMarkdown = [
|
|
2602
|
+
`**Main risk**
|
|
2603
|
+
${renderSignalStoryPlainText(primary.sentence)}`,
|
|
2604
|
+
`**Why it matters**
|
|
2605
|
+
${renderSignalStoryPlainText(primary.rationale ?? [])}`,
|
|
2606
|
+
`**Recommended actions**
|
|
2607
|
+
${primary.action?.label ?? "Rerun the review after remediation."}`,
|
|
2608
|
+
"**Evidence used**\n**Gate status**, **Well-Architected score**, **validation totals**, **policy totals**, and **monthly cost**."
|
|
2609
|
+
].join("\n\n");
|
|
2610
|
+
return {
|
|
2611
|
+
enabled: true,
|
|
2612
|
+
status: "fallback",
|
|
2613
|
+
fallbackUsed: true,
|
|
2614
|
+
warnings: [],
|
|
2615
|
+
shortSummary,
|
|
2616
|
+
detailsMarkdown,
|
|
2617
|
+
markdown: renderGithubSummary(stories, {
|
|
2618
|
+
title: "CloudEval review summary"
|
|
2619
|
+
})
|
|
2620
|
+
};
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2544
2623
|
// src/reviewCommand.ts
|
|
2545
2624
|
var DIRTY_REVIEW_MESSAGE = "Reviews pushed commits only. Add --ignore-dirty to review HEAD anyway.";
|
|
2546
2625
|
var runGit = async (cwd, args) => {
|
|
@@ -3167,22 +3246,184 @@ var compactCostRowsForChart = (rows, totalAmount, currency, options = {}) => {
|
|
|
3167
3246
|
return result;
|
|
3168
3247
|
};
|
|
3169
3248
|
var markdownLink = (label, url) => url ? `[${label}](${url})` : label;
|
|
3170
|
-
var
|
|
3249
|
+
var shieldSegment = (value) => encodeURIComponent(value.trim().replace(/-/g, "--").replace(/_/g, "__"));
|
|
3250
|
+
var badgeLink = ({
|
|
3251
|
+
label,
|
|
3252
|
+
message,
|
|
3253
|
+
color,
|
|
3254
|
+
url
|
|
3255
|
+
}) => {
|
|
3256
|
+
if (!url) {
|
|
3257
|
+
return void 0;
|
|
3258
|
+
}
|
|
3259
|
+
return `[}-${shieldSegment(message)}-${color}?style=flat-square)](${url})`;
|
|
3260
|
+
};
|
|
3261
|
+
var openInCloudEvalBadges = (links) => {
|
|
3171
3262
|
if (!links) {
|
|
3172
3263
|
return [];
|
|
3173
3264
|
}
|
|
3174
3265
|
const reports = asRecord(links.reports);
|
|
3175
3266
|
const downloads = asRecord(links.downloads);
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3267
|
+
return [
|
|
3268
|
+
badgeLink({
|
|
3269
|
+
label: "Project",
|
|
3270
|
+
message: "preview",
|
|
3271
|
+
color: "2563eb",
|
|
3272
|
+
url: typeof links.project === "string" ? links.project : void 0
|
|
3273
|
+
}),
|
|
3274
|
+
badgeLink({
|
|
3275
|
+
label: "Report",
|
|
3276
|
+
message: "architecture",
|
|
3277
|
+
color: "16a34a",
|
|
3278
|
+
url: typeof reports.architecture === "string" ? reports.architecture : void 0
|
|
3279
|
+
}),
|
|
3280
|
+
badgeLink({
|
|
3281
|
+
label: "Cost",
|
|
3282
|
+
message: "drilldown",
|
|
3283
|
+
color: "0f766e",
|
|
3284
|
+
url: typeof reports.cost === "string" ? reports.cost : void 0
|
|
3285
|
+
}),
|
|
3286
|
+
badgeLink({
|
|
3287
|
+
label: "Validation",
|
|
3288
|
+
message: "details",
|
|
3289
|
+
color: "d97706",
|
|
3290
|
+
url: typeof reports.validation === "string" ? reports.validation : void 0
|
|
3291
|
+
}),
|
|
3292
|
+
badgeLink({
|
|
3293
|
+
label: "PDF",
|
|
3294
|
+
message: "download",
|
|
3295
|
+
color: "7c3aed",
|
|
3296
|
+
url: typeof downloads.pdf === "string" ? downloads.pdf : void 0
|
|
3297
|
+
}),
|
|
3298
|
+
badgeLink({
|
|
3299
|
+
label: "Workflow",
|
|
3300
|
+
message: "run",
|
|
3301
|
+
color: "475569",
|
|
3302
|
+
url: typeof links.workflowRun === "string" ? links.workflowRun : void 0
|
|
3303
|
+
}),
|
|
3304
|
+
badgeLink({
|
|
3305
|
+
label: "Artifacts",
|
|
3306
|
+
message: "review",
|
|
3307
|
+
color: "475569",
|
|
3308
|
+
url: typeof downloads.reviewArtifacts === "string" ? downloads.reviewArtifacts : void 0
|
|
3309
|
+
})
|
|
3310
|
+
].filter((entry) => Boolean(entry));
|
|
3311
|
+
};
|
|
3312
|
+
var signalTableCell = (summaryLine) => {
|
|
3313
|
+
const match = summaryLine.match(/^(\S+)\s+[^:]+:\s*(.+)$/);
|
|
3314
|
+
if (!match) {
|
|
3315
|
+
return compactMarkdownCell(summaryLine);
|
|
3316
|
+
}
|
|
3317
|
+
return `${match[1]} **${compactMarkdownCell(match[2])}**`;
|
|
3318
|
+
};
|
|
3319
|
+
var reviewDecisionLine = ({
|
|
3320
|
+
gateStatus,
|
|
3321
|
+
score,
|
|
3322
|
+
rating
|
|
3323
|
+
}) => {
|
|
3324
|
+
if (gateStatus === "FAIL") {
|
|
3325
|
+
return `${statusIcon(gateStatus)} **FAIL** - configured gates failed. Do not merge until the action queue is resolved and CloudEval is rerun.`;
|
|
3326
|
+
}
|
|
3327
|
+
if (gateStatus === "WARN") {
|
|
3328
|
+
return `${statusIcon(gateStatus)} **WARN** - configured gates are warning-only or non-blocking for this run. Review the action queue before merge.`;
|
|
3329
|
+
}
|
|
3330
|
+
if (rating === "CRITICAL" || rating === "POOR") {
|
|
3331
|
+
return `${statusIcon(gateStatus)} **PASS** - configured gates passed, but observed Well-Architected posture is **${formatScore(score)} (${rating})**. Tighten gate thresholds if this posture should block pull requests.`;
|
|
3332
|
+
}
|
|
3333
|
+
return `${statusIcon(gateStatus)} **PASS** - configured gates passed for this review. Use the drilldowns below to keep the posture improving.`;
|
|
3334
|
+
};
|
|
3335
|
+
var strongestPillarRisk = (pillars) => pillars.map((pillar) => ({
|
|
3336
|
+
label: String(pillar.label ?? pillar.id ?? "Well-Architected pillar"),
|
|
3337
|
+
score: numberFrom(pillar.score)
|
|
3338
|
+
})).filter((pillar) => pillar.score !== void 0).sort((left, right) => left.score - right.score).map((pillar) => ({
|
|
3339
|
+
...pillar,
|
|
3340
|
+
rating: scoreRating(pillar.score)
|
|
3341
|
+
}))[0];
|
|
3342
|
+
var reviewActionItems = ({
|
|
3343
|
+
data,
|
|
3344
|
+
pillars,
|
|
3345
|
+
cost,
|
|
3346
|
+
validation
|
|
3347
|
+
}) => {
|
|
3348
|
+
const endpointActions = Array.isArray(data.aiSummary?.recommendedActions) ? data.aiSummary.recommendedActions.map((action) => compactMarkdownCell(action, "")).filter(Boolean) : [];
|
|
3349
|
+
const failedUnitTests = numberFrom(validation?.unitTests?.failed) ?? 0;
|
|
3350
|
+
const failedPolicyChecks = numberFrom(validation?.policyChecks?.failed) ?? 0;
|
|
3351
|
+
const weakest = strongestPillarRisk(pillars);
|
|
3352
|
+
const currentCost = numberFrom(cost?.amount);
|
|
3353
|
+
const savings = numberFrom(data.gate?.cost?.estimatedSavings?.amount);
|
|
3354
|
+
const currency = cost?.currency ?? data.gate?.cost?.estimatedSavings?.currency;
|
|
3355
|
+
const actions = [...endpointActions];
|
|
3356
|
+
if (failedUnitTests > 0 || failedPolicyChecks > 0) {
|
|
3357
|
+
const parts = [];
|
|
3358
|
+
if (failedUnitTests > 0) {
|
|
3359
|
+
parts.push(`${displayNumber(failedUnitTests)} failed unit tests`);
|
|
3360
|
+
}
|
|
3361
|
+
if (failedPolicyChecks > 0) {
|
|
3362
|
+
parts.push(`${displayNumber(failedPolicyChecks)} failed policy checks`);
|
|
3363
|
+
}
|
|
3364
|
+
actions.push(
|
|
3365
|
+
`**Fix validation failures** - resolve ${joinReadableList(parts)} and rerun CloudEval review.`
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
if (weakest) {
|
|
3369
|
+
actions.push(
|
|
3370
|
+
`**Prioritize ${weakest.label}** - weakest pillar is **${formatScore(weakest.score)} (${weakest.rating ?? "UNKNOWN"})**.`
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
if (currentCost !== void 0) {
|
|
3374
|
+
const savingsText = savings !== void 0 && savings > 0 ? `; review the estimated **${formatMonthlyMoney(savings, currency)}** savings` : "";
|
|
3375
|
+
actions.push(
|
|
3376
|
+
`**Review cost drivers** - current monthly cost is **${formatMonthlyMoney(currentCost, currency)}**${savingsText}.`
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
if (Array.isArray(data.gate?.failures)) {
|
|
3380
|
+
for (const failure of data.gate.failures) {
|
|
3381
|
+
actions.push(`**Address gate failure** - ${compactMarkdownCell(failure)}.`);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
actions.push("**Rerun CloudEval** - confirm the updated gate, reports, and PR comment after remediation.");
|
|
3385
|
+
const unique2 = [];
|
|
3386
|
+
for (const action of actions) {
|
|
3387
|
+
if (!unique2.includes(action)) {
|
|
3388
|
+
unique2.push(action);
|
|
3389
|
+
}
|
|
3390
|
+
if (unique2.length >= 3) {
|
|
3391
|
+
break;
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
return unique2.map((action, index) => `${index + 1}. ${action}`);
|
|
3395
|
+
};
|
|
3396
|
+
var mermaidAxisId = (label) => {
|
|
3397
|
+
const normalized = normalizeKey(label).replace(/[^a-z0-9_]/g, "_");
|
|
3398
|
+
return normalized || "pillar";
|
|
3399
|
+
};
|
|
3400
|
+
var wellArchitectedRadarLines = (pillars) => {
|
|
3401
|
+
const scored = pillars.map((pillar) => {
|
|
3402
|
+
const label = String(pillar.label ?? pillar.id ?? "Pillar");
|
|
3403
|
+
const score = numberFrom(pillar.score);
|
|
3404
|
+
return score === void 0 ? void 0 : {
|
|
3405
|
+
id: mermaidAxisId(label),
|
|
3406
|
+
label,
|
|
3407
|
+
score
|
|
3408
|
+
};
|
|
3409
|
+
}).filter(
|
|
3410
|
+
(pillar) => pillar !== void 0
|
|
3411
|
+
);
|
|
3412
|
+
if (scored.length < 3) {
|
|
3413
|
+
return [];
|
|
3414
|
+
}
|
|
3415
|
+
return [
|
|
3416
|
+
"```mermaid",
|
|
3417
|
+
"radar-beta",
|
|
3418
|
+
" title Well-Architected posture",
|
|
3419
|
+
` axis ${scored.map((pillar) => `${pillar.id}["${mermaidLabel(pillar.label)}"]`).join(", ")}`,
|
|
3420
|
+
` curve current["Current"]{${scored.map((pillar) => trimNumber(pillar.score, 3)).join(", ")}}`,
|
|
3421
|
+
" max 100",
|
|
3422
|
+
" min 0",
|
|
3423
|
+
"```",
|
|
3424
|
+
"",
|
|
3425
|
+
"_If GitHub does not render Mermaid radar charts yet, use the table below as the fallback._"
|
|
3426
|
+
];
|
|
3186
3427
|
};
|
|
3187
3428
|
var monthlyCostImpactLines = (currentAmount, savingsAmount, currency) => {
|
|
3188
3429
|
const current = numberFrom(currentAmount);
|
|
@@ -3665,7 +3906,7 @@ var renderAiSummarySections = (shortSummary, detailsMarkdown) => {
|
|
|
3665
3906
|
lines.push(
|
|
3666
3907
|
"",
|
|
3667
3908
|
"<details>",
|
|
3668
|
-
"<summary>AI
|
|
3909
|
+
"<summary><strong>Detailed AI reviewer note - evidence, reasoning, and next actions</strong></summary>",
|
|
3669
3910
|
"",
|
|
3670
3911
|
detailsMarkdown.trim(),
|
|
3671
3912
|
"",
|
|
@@ -3698,7 +3939,7 @@ var reviewSurface = () => {
|
|
|
3698
3939
|
const ref = String(process.env.GITHUB_REF ?? "").toLowerCase();
|
|
3699
3940
|
return event.startsWith("pull_request") || ref.startsWith("refs/pull/") ? "pull_request" : "local_review";
|
|
3700
3941
|
};
|
|
3701
|
-
var buildReviewSummaryPayload = (data) => ({
|
|
3942
|
+
var buildReviewSummaryPayload = (data, preferences = {}) => ({
|
|
3702
3943
|
source: process.env.GITHUB_ACTIONS === "true" ? "github_action" : "cli",
|
|
3703
3944
|
surface: reviewSurface(),
|
|
3704
3945
|
project: data.project ?? { id: data.projectId },
|
|
@@ -3717,7 +3958,12 @@ var buildReviewSummaryPayload = (data) => ({
|
|
|
3717
3958
|
validation: data.gate?.validation ?? {},
|
|
3718
3959
|
policy: data.gate?.validation?.policy ?? data.gate?.policy ?? {},
|
|
3719
3960
|
architecture_signals: data.gate?.architecture ?? {},
|
|
3720
|
-
changed_files: data.changedFiles ?? []
|
|
3961
|
+
changed_files: data.changedFiles ?? [],
|
|
3962
|
+
ai_preferences: {
|
|
3963
|
+
mode: preferences.mode ?? "ask",
|
|
3964
|
+
...preferences.agentProfileId ? { agent_profile_id: preferences.agentProfileId } : {},
|
|
3965
|
+
...preferences.model ? { model: preferences.model } : {}
|
|
3966
|
+
}
|
|
3721
3967
|
});
|
|
3722
3968
|
var deterministicAiSummary = (data, error) => {
|
|
3723
3969
|
const score = data.gate?.wellArchitected?.overall?.score ?? data.gate?.overallScore;
|
|
@@ -3732,6 +3978,27 @@ var deterministicAiSummary = (data, error) => {
|
|
|
3732
3978
|
)[0] : void 0;
|
|
3733
3979
|
const weakestPillarLabel = weakestPillar?.label ?? weakestPillar?.id ?? "the weakest Well-Architected pillar";
|
|
3734
3980
|
const highRisk = numberFrom(data.gate?.wellArchitected?.risks?.high) ?? 0;
|
|
3981
|
+
const signalStorySummary = buildSignalStoryReviewFallback({
|
|
3982
|
+
gateStatus: String(data.gate?.status ?? "UNKNOWN").toUpperCase(),
|
|
3983
|
+
score: formatScore(score),
|
|
3984
|
+
rating,
|
|
3985
|
+
failedTests,
|
|
3986
|
+
policyStatus,
|
|
3987
|
+
monthlyCost: formatMonthlyMoney(cost?.amount, cost?.currency),
|
|
3988
|
+
weakestPillar: weakestPillarLabel
|
|
3989
|
+
});
|
|
3990
|
+
if (signalStorySummary) {
|
|
3991
|
+
const signalStoryShortSummary = String(signalStorySummary.shortSummary ?? "").trim();
|
|
3992
|
+
const signalStoryDetailsMarkdown = String(signalStorySummary.detailsMarkdown ?? "").trim();
|
|
3993
|
+
return {
|
|
3994
|
+
...signalStorySummary,
|
|
3995
|
+
warnings: error ? [`Review summary endpoint failed: ${error}`] : [],
|
|
3996
|
+
markdown: renderAiSummarySections(
|
|
3997
|
+
signalStoryShortSummary,
|
|
3998
|
+
signalStoryDetailsMarkdown
|
|
3999
|
+
)
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
3735
4002
|
const summary = [
|
|
3736
4003
|
`CloudEval review completed with **${String(data.gate?.status ?? "UNKNOWN").toUpperCase()}**.`,
|
|
3737
4004
|
`Well-Architected posture is **${formatScore(score)} (${rating})**, validation has **${displayNumber(failedTests)} failed unit tests**, policy checks are **${policyStatus}**, and monthly cost is **${formatMonthlyMoney(cost?.amount, cost?.currency)}**.`,
|
|
@@ -3758,7 +4025,11 @@ Fix **${displayNumber(failedTests)} failed unit tests**, address **${weakestPill
|
|
|
3758
4025
|
};
|
|
3759
4026
|
var generateAiSummary = async (input) => {
|
|
3760
4027
|
try {
|
|
3761
|
-
const payload = buildReviewSummaryPayload(input.data
|
|
4028
|
+
const payload = buildReviewSummaryPayload(input.data, {
|
|
4029
|
+
model: input.model,
|
|
4030
|
+
mode: input.mode,
|
|
4031
|
+
agentProfileId: input.agentProfileId
|
|
4032
|
+
});
|
|
3762
4033
|
const response = await fetchCloudEvalJson({
|
|
3763
4034
|
baseUrl: input.baseUrl,
|
|
3764
4035
|
authToken: input.token,
|
|
@@ -3808,7 +4079,8 @@ var buildMarkdownSummary = (data) => {
|
|
|
3808
4079
|
const repository = String(data.repo ?? "unknown repository");
|
|
3809
4080
|
const ref = String(data.ref ?? "unknown ref");
|
|
3810
4081
|
const commit = String(data.commitSha ?? "unknown").slice(0, 12);
|
|
3811
|
-
const
|
|
4082
|
+
const pillars = Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars : [];
|
|
4083
|
+
const pillarLines = pillars.length ? pillars.map((pillar) => {
|
|
3812
4084
|
const rating = scoreRating(pillar.score);
|
|
3813
4085
|
return `| ${pillar.label} | **${formatScore(pillar.score)}** | ${scoreRatingIcon(rating)} ${rating ?? "UNKNOWN"} |`;
|
|
3814
4086
|
}) : [];
|
|
@@ -3834,47 +4106,87 @@ var buildMarkdownSummary = (data) => {
|
|
|
3834
4106
|
);
|
|
3835
4107
|
const costPieRows = namedResourceCosts.length ? positiveResourceCosts : costServices.filter((service) => service.amount > 0);
|
|
3836
4108
|
const costPieTitle = namedResourceCosts.length ? "Monthly cost by resource" : "Monthly cost by service";
|
|
3837
|
-
const
|
|
4109
|
+
const linkBadges = openInCloudEvalBadges(data.links);
|
|
3838
4110
|
const architectureLines = architectureSignalLines({
|
|
3839
4111
|
architecture,
|
|
3840
4112
|
costServices,
|
|
3841
4113
|
costCurrency: cost?.currency,
|
|
3842
4114
|
highRiskFindings: data.gate?.wellArchitected?.risks?.high,
|
|
3843
|
-
pillars
|
|
4115
|
+
pillars
|
|
3844
4116
|
});
|
|
3845
4117
|
const validationRows = validationFailureRows(validation);
|
|
3846
4118
|
const overallRating = scoreRating(score);
|
|
4119
|
+
const actionLines = reviewActionItems({
|
|
4120
|
+
data,
|
|
4121
|
+
pillars,
|
|
4122
|
+
cost,
|
|
4123
|
+
validation
|
|
4124
|
+
});
|
|
4125
|
+
const radarLines = wellArchitectedRadarLines(pillars);
|
|
3847
4126
|
const lines = [
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
4127
|
+
"## CloudEval infrastructure review",
|
|
4128
|
+
"",
|
|
4129
|
+
"| Signal | Result |",
|
|
4130
|
+
"| --- | --- |",
|
|
4131
|
+
`| Merge gate | ${statusIcon(data.gate?.status)} **${gateStatus}** |`,
|
|
4132
|
+
`| Observed posture | ${scoreRatingIcon(overallRating)} **${formatScore(score)} (${overallRating ?? "UNKNOWN"})** |`,
|
|
4133
|
+
`| Validation | ${signalTableCell(validationSummaryLine(validation))} |`,
|
|
4134
|
+
`| Policy | ${signalTableCell(policySummaryLine(validation))} |`,
|
|
4135
|
+
`| Cost | ${signalTableCell(costSummaryLine(cost))} |`
|
|
4136
|
+
];
|
|
4137
|
+
if (linkBadges.length) {
|
|
4138
|
+
lines.push("", "### Links", "", linkBadges.join(" "));
|
|
4139
|
+
}
|
|
4140
|
+
lines.push(
|
|
3853
4141
|
"",
|
|
3854
|
-
"
|
|
4142
|
+
"### Decision",
|
|
4143
|
+
"",
|
|
4144
|
+
reviewDecisionLine({ gateStatus, score, rating: overallRating }),
|
|
4145
|
+
"",
|
|
4146
|
+
"<details>",
|
|
4147
|
+
"<summary><strong>Source</strong></summary>",
|
|
3855
4148
|
"",
|
|
3856
4149
|
`- **CloudEval project**: ${projectDisplay}`,
|
|
3857
4150
|
`- **Repository**: \`${repository}\``,
|
|
3858
4151
|
`- **Ref**: \`${ref}\``,
|
|
3859
|
-
`- **Commit**: \`${commit}
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
}
|
|
4152
|
+
`- **Commit**: \`${commit}\``,
|
|
4153
|
+
"",
|
|
4154
|
+
"</details>"
|
|
4155
|
+
);
|
|
3864
4156
|
if (data.aiSummary?.markdown) {
|
|
3865
|
-
lines.push("", "
|
|
4157
|
+
lines.push("", "### AI summary", "", data.aiSummary.markdown);
|
|
4158
|
+
}
|
|
4159
|
+
if (actionLines.length) {
|
|
4160
|
+
lines.push(
|
|
4161
|
+
"",
|
|
4162
|
+
"<details open>",
|
|
4163
|
+
`<summary><strong>Action queue - ${actionLines.length} recommended fixes</strong></summary>`,
|
|
4164
|
+
"",
|
|
4165
|
+
...actionLines,
|
|
4166
|
+
"",
|
|
4167
|
+
"</details>"
|
|
4168
|
+
);
|
|
3866
4169
|
}
|
|
3867
4170
|
if (Array.isArray(data.gate?.failures) && data.gate.failures.length) {
|
|
3868
|
-
lines.push(
|
|
4171
|
+
lines.push(
|
|
4172
|
+
"",
|
|
4173
|
+
"<details>",
|
|
4174
|
+
"<summary><strong>Gate failures</strong></summary>",
|
|
4175
|
+
"",
|
|
4176
|
+
...data.gate.failures.map((failure) => `- ${failure}`),
|
|
4177
|
+
"",
|
|
4178
|
+
"</details>"
|
|
4179
|
+
);
|
|
3869
4180
|
}
|
|
3870
4181
|
if (pillarLines.length) {
|
|
3871
4182
|
lines.push(
|
|
3872
4183
|
"",
|
|
3873
4184
|
"<details>",
|
|
3874
|
-
"<summary>Well-Architected drilldown</summary>",
|
|
4185
|
+
"<summary><strong>Well-Architected drilldown</strong></summary>",
|
|
3875
4186
|
"",
|
|
3876
4187
|
...riskLines,
|
|
3877
4188
|
"",
|
|
4189
|
+
...radarLines.length ? [...radarLines, ""] : [],
|
|
3878
4190
|
"| Pillar | Score | Rating |",
|
|
3879
4191
|
"| --- | ---: | --- |",
|
|
3880
4192
|
...pillarLines,
|
|
@@ -3921,7 +4233,7 @@ var buildMarkdownSummary = (data) => {
|
|
|
3921
4233
|
lines.push(
|
|
3922
4234
|
"",
|
|
3923
4235
|
"<details>",
|
|
3924
|
-
"<summary>Cost drilldown</summary>",
|
|
4236
|
+
"<summary><strong>Cost drilldown</strong></summary>",
|
|
3925
4237
|
"",
|
|
3926
4238
|
...costLines,
|
|
3927
4239
|
"",
|
|
@@ -3933,7 +4245,7 @@ var buildMarkdownSummary = (data) => {
|
|
|
3933
4245
|
lines.push(
|
|
3934
4246
|
"",
|
|
3935
4247
|
"<details>",
|
|
3936
|
-
`<summary>${validationRows.length ? "Validation failures" : "Validation details"}</summary>`,
|
|
4248
|
+
`<summary><strong>${validationRows.length ? "Validation failures" : "Validation details"}</strong></summary>`,
|
|
3937
4249
|
"",
|
|
3938
4250
|
...validationDetailLines(validation),
|
|
3939
4251
|
...validationRows.length ? [
|
|
@@ -3950,7 +4262,7 @@ var buildMarkdownSummary = (data) => {
|
|
|
3950
4262
|
lines.push(
|
|
3951
4263
|
"",
|
|
3952
4264
|
"<details>",
|
|
3953
|
-
"<summary>Architecture signals</summary>",
|
|
4265
|
+
"<summary><strong>Architecture signals</strong></summary>",
|
|
3954
4266
|
"",
|
|
3955
4267
|
...architectureLines,
|
|
3956
4268
|
"",
|
|
@@ -16869,7 +17181,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option("--b
|
|
|
16869
17181
|
const { assertSecureBaseUrl } = await import("./dist-6LEMVXIY.js");
|
|
16870
17182
|
const [{ render }, { App }] = await Promise.all([
|
|
16871
17183
|
import("ink"),
|
|
16872
|
-
import("./App-
|
|
17184
|
+
import("./App-SKVX7NAF.js")
|
|
16873
17185
|
]);
|
|
16874
17186
|
const baseUrl = await resolveBaseUrl(options, command);
|
|
16875
17187
|
assertSecureBaseUrl(baseUrl);
|
|
@@ -16930,7 +17242,7 @@ program.command("chat").description("Start an interactive chat session").option(
|
|
|
16930
17242
|
const { assertSecureBaseUrl } = await import("./dist-6LEMVXIY.js");
|
|
16931
17243
|
const [{ render }, { App }] = await Promise.all([
|
|
16932
17244
|
import("ink"),
|
|
16933
|
-
import("./App-
|
|
17245
|
+
import("./App-SKVX7NAF.js")
|
|
16934
17246
|
]);
|
|
16935
17247
|
const baseUrl = await resolveBaseUrl(options, command);
|
|
16936
17248
|
assertSecureBaseUrl(baseUrl);
|
|
@@ -17722,7 +18034,7 @@ Error: ${errorMsg}
|
|
|
17722
18034
|
program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
|
|
17723
18035
|
const { render } = await import("ink");
|
|
17724
18036
|
const BannerPreview = React.lazy(async () => ({
|
|
17725
|
-
default: (await import("./Banner-
|
|
18037
|
+
default: (await import("./Banner-CRBHEOTC.js")).Banner
|
|
17726
18038
|
}));
|
|
17727
18039
|
render(
|
|
17728
18040
|
/* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(BannerPreview, { disable: false }) })
|
package/package.json
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ganakailabs/cloudeval-cli",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.3",
|
|
4
4
|
"license": "LicenseRef-CloudEval-CLI",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "Review Cloud infra-as-code and live environments from CLI, CI, and MCP agents.",
|
|
7
7
|
"author": "Ganak AI Labs (https://cloudeval.ai)",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"cloudeval",
|
|
10
|
-
"cloud",
|
|
11
10
|
"azure",
|
|
12
|
-
"
|
|
13
|
-
"mcp",
|
|
14
|
-
"agents",
|
|
15
|
-
"automation",
|
|
16
|
-
"iac",
|
|
11
|
+
"azure-devops",
|
|
17
12
|
"arm-template",
|
|
13
|
+
"bicep",
|
|
14
|
+
"infrastructure-as-code",
|
|
15
|
+
"iac-review",
|
|
16
|
+
"cloud-architecture",
|
|
17
|
+
"cloud-cost",
|
|
18
18
|
"well-architected",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
19
|
+
"finops",
|
|
20
|
+
"devops",
|
|
21
|
+
"github-actions",
|
|
22
|
+
"ci-cd",
|
|
23
|
+
"mcp",
|
|
24
|
+
"agents"
|
|
22
25
|
],
|
|
23
26
|
"homepage": "https://docs.cloudeval.ai/reference/cli-overview",
|
|
24
27
|
"repository": {
|
|
@@ -83,6 +86,7 @@
|
|
|
83
86
|
"marked-terminal": "^7.3.0",
|
|
84
87
|
"react": "^18.3.1",
|
|
85
88
|
"react-devtools-core": "^4.28.5",
|
|
89
|
+
"signalstory": "https://github.com/ganakailabs/signalstory/archive/refs/tags/v0.1.0.tar.gz",
|
|
86
90
|
"sql.js": "^1.14.1"
|
|
87
91
|
},
|
|
88
92
|
"devDependencies": {
|
package/sbom.spdx.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
{
|
|
15
15
|
"SPDXID": "SPDXRef-Package-CloudEval-CLI",
|
|
16
16
|
"name": "CloudEval CLI",
|
|
17
|
-
"versionInfo": "0.30.
|
|
17
|
+
"versionInfo": "0.30.3",
|
|
18
18
|
"downloadLocation": "https://github.com/ganakailabs/cloudeval-cli",
|
|
19
19
|
"filesAnalyzed": false,
|
|
20
20
|
"licenseConcluded": "LicenseRef-CloudEval-CLI",
|
|
@@ -2254,6 +2254,17 @@
|
|
|
2254
2254
|
"copyrightText": "Ben Coe",
|
|
2255
2255
|
"summary": "when you want to fire an event no matter how a process exits."
|
|
2256
2256
|
},
|
|
2257
|
+
{
|
|
2258
|
+
"SPDXID": "SPDXRef-Package-signalstory-0.1.0",
|
|
2259
|
+
"name": "signalstory",
|
|
2260
|
+
"versionInfo": "0.1.0",
|
|
2261
|
+
"downloadLocation": "NOASSERTION",
|
|
2262
|
+
"filesAnalyzed": false,
|
|
2263
|
+
"licenseConcluded": "Apache-2.0",
|
|
2264
|
+
"licenseDeclared": "Apache-2.0",
|
|
2265
|
+
"copyrightText": "NOASSERTION",
|
|
2266
|
+
"summary": "Composable signal-to-story generation for evidence-grounded product, ops, and review narratives."
|
|
2267
|
+
},
|
|
2257
2268
|
{
|
|
2258
2269
|
"SPDXID": "SPDXRef-Package-skin-tone-2.0.0",
|
|
2259
2270
|
"name": "skin-tone",
|
|
@@ -3678,6 +3689,11 @@
|
|
|
3678
3689
|
"relationshipType": "DEPENDS_ON",
|
|
3679
3690
|
"relatedSpdxElement": "SPDXRef-Package-signal-exit-3.0.7"
|
|
3680
3691
|
},
|
|
3692
|
+
{
|
|
3693
|
+
"spdxElementId": "SPDXRef-Package-CloudEval-CLI",
|
|
3694
|
+
"relationshipType": "DEPENDS_ON",
|
|
3695
|
+
"relatedSpdxElement": "SPDXRef-Package-signalstory-0.1.0"
|
|
3696
|
+
},
|
|
3681
3697
|
{
|
|
3682
3698
|
"spdxElementId": "SPDXRef-Package-CloudEval-CLI",
|
|
3683
3699
|
"relationshipType": "DEPENDS_ON",
|