@ganakailabs/cloudeval-cli 0.24.3 → 0.25.0

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/dist/cli.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  } from "./chunk-KBHRBGSX.js";
37
37
  import {
38
38
  CLI_VERSION
39
- } from "./chunk-UZDPQSE5.js";
39
+ } from "./chunk-LKVKOGVL.js";
40
40
 
41
41
  // src/runtime/prepareInk.ts
42
42
  import fs from "fs";
@@ -135,9 +135,9 @@ ensureInkRuntimeEnvironment();
135
135
  // src/cli.tsx
136
136
  import React from "react";
137
137
  import { Command } from "commander";
138
- import { promises as fs12 } from "fs";
138
+ import { promises as fs13 } from "fs";
139
139
  import os6 from "os";
140
- import path9 from "path";
140
+ import path10 from "path";
141
141
 
142
142
  // src/shellCompletion.ts
143
143
  var normalizeCompletionShell = (shell) => {
@@ -437,6 +437,33 @@ var cliCommands = [
437
437
  "reports rules"
438
438
  ]
439
439
  },
440
+ {
441
+ name: "review",
442
+ description: "Sync a GitHub-backed CloudEval project from the pushed commit and evaluate report gates",
443
+ domain: "reports",
444
+ options: [
445
+ ...authOptions,
446
+ "--project",
447
+ "--repo",
448
+ "--ref",
449
+ "--commit-sha",
450
+ "--source-root",
451
+ "--config",
452
+ "--no-wait",
453
+ "--wait-timeout",
454
+ "--poll-interval",
455
+ "--no-ai-summary",
456
+ "--ignore-dirty",
457
+ "--output",
458
+ "--format",
459
+ "--quiet",
460
+ "--progress",
461
+ "--model",
462
+ "--profile",
463
+ "--help"
464
+ ],
465
+ workflows: ["github review", "pr gate", "shift-left review"]
466
+ },
440
467
  {
441
468
  name: "recipes",
442
469
  description: "CloudEval reusable recipes and agent skills",
@@ -1436,10 +1463,10 @@ var writeFormattedOutput = async (input) => {
1436
1463
  process.stdout.write(text);
1437
1464
  };
1438
1465
  var writePrivateOutputFile = async (output, text) => {
1439
- const fs13 = await import("fs/promises");
1440
- await fs13.writeFile(output, text, { encoding: "utf8", mode: 384 });
1466
+ const fs14 = await import("fs/promises");
1467
+ await fs14.writeFile(output, text, { encoding: "utf8", mode: 384 });
1441
1468
  if (process.platform !== "win32") {
1442
- await fs13.chmod(output, 384);
1469
+ await fs14.chmod(output, 384);
1443
1470
  }
1444
1471
  };
1445
1472
 
@@ -1787,8 +1814,8 @@ var writeReport = async (report, options, tuiDefault) => {
1787
1814
  const textFormat = format === "text" || format === "table" ? "summary" : format;
1788
1815
  const text = serializeReportOutput(report, { format: textFormat, mode });
1789
1816
  if (options.output) {
1790
- const fs13 = await import("fs/promises");
1791
- await fs13.writeFile(options.output, text, "utf8");
1817
+ const fs14 = await import("fs/promises");
1818
+ await fs14.writeFile(options.output, text, "utf8");
1792
1819
  return;
1793
1820
  }
1794
1821
  process.stdout.write(text);
@@ -1823,16 +1850,16 @@ var writeDownloadPayload = async (input) => {
1823
1850
  );
1824
1851
  return [];
1825
1852
  }
1826
- const fs13 = await import("fs/promises");
1827
- const path10 = await import("path");
1828
- await fs13.mkdir(path10.dirname(input.output), { recursive: true });
1853
+ const fs14 = await import("fs/promises");
1854
+ const path11 = await import("path");
1855
+ await fs14.mkdir(path11.dirname(input.output), { recursive: true });
1829
1856
  const text = formatOutput({
1830
1857
  command: input.command,
1831
1858
  data: input.payload,
1832
1859
  format: input.format,
1833
1860
  frontendUrl: input.frontendUrl
1834
1861
  });
1835
- await fs13.writeFile(input.output, text, "utf8");
1862
+ await fs14.writeFile(input.output, text, "utf8");
1836
1863
  return [input.output];
1837
1864
  };
1838
1865
  var resolveMachineFormat = (requested) => {
@@ -1981,14 +2008,14 @@ var registerReportsCommand = (program2, deps) => {
1981
2008
  );
1982
2009
  const data = reportTypes.length === 1 ? payload[reportTypes[0] === "architecture" ? "waf" : reportTypes[0]] : payload;
1983
2010
  if (options.output && reportTypes.length > 1) {
1984
- const fs13 = await import("fs/promises");
1985
- const path10 = await import("path");
1986
- const stat = await fs13.stat(options.output).catch(() => void 0);
1987
- if (stat?.isDirectory() || !path10.extname(options.output)) {
1988
- await fs13.mkdir(options.output, { recursive: true });
2011
+ const fs14 = await import("fs/promises");
2012
+ const path11 = await import("path");
2013
+ const stat = await fs14.stat(options.output).catch(() => void 0);
2014
+ if (stat?.isDirectory() || !path11.extname(options.output)) {
2015
+ await fs14.mkdir(options.output, { recursive: true });
1989
2016
  const files = [];
1990
2017
  for (const [key, value] of Object.entries(payload)) {
1991
- const file = path10.join(options.output, `${projectId}-${key}-report.json`);
2018
+ const file = path11.join(options.output, `${projectId}-${key}-report.json`);
1992
2019
  files.push(
1993
2020
  ...await writeDownloadPayload({
1994
2021
  command: "reports download",
@@ -2217,9 +2244,546 @@ var registerReportsCommand = (program2, deps) => {
2217
2244
  );
2218
2245
  };
2219
2246
 
2247
+ // src/reviewCommand.ts
2248
+ import fs2 from "fs/promises";
2249
+ import path2 from "path";
2250
+ import { spawn } from "child_process";
2251
+
2252
+ // src/apiClient.ts
2253
+ var responseErrorMessage = async (response) => {
2254
+ const text = await response.text();
2255
+ if (!text) {
2256
+ return `request failed with status ${response.status}`;
2257
+ }
2258
+ try {
2259
+ const payload = JSON.parse(text);
2260
+ const detail = payload?.detail ?? payload?.message ?? payload?.error;
2261
+ if (typeof detail === "string") {
2262
+ return detail;
2263
+ }
2264
+ if (detail) {
2265
+ return JSON.stringify(detail);
2266
+ }
2267
+ } catch {
2268
+ }
2269
+ return text;
2270
+ };
2271
+ var fetchCloudEvalJson = async ({
2272
+ baseUrl,
2273
+ authToken,
2274
+ path: path11,
2275
+ method = "GET",
2276
+ query = {},
2277
+ body,
2278
+ idempotencyKey
2279
+ }) => {
2280
+ const url = new URL(`${normalizeApiBase(baseUrl)}${path11}`);
2281
+ for (const [key, value] of Object.entries(query)) {
2282
+ if (value !== void 0 && value !== null && value !== "") {
2283
+ url.searchParams.set(key, String(value));
2284
+ }
2285
+ }
2286
+ const response = await fetch(url, {
2287
+ method,
2288
+ headers: {
2289
+ ...getCLIHeaders(authToken),
2290
+ ...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
2291
+ },
2292
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
2293
+ });
2294
+ if (!response.ok) {
2295
+ throw new Error(await responseErrorMessage(response));
2296
+ }
2297
+ return await response.json();
2298
+ };
2299
+
2300
+ // src/reviewCommand.ts
2301
+ var DIRTY_REVIEW_MESSAGE = "Reviews pushed commits only. Add --ignore-dirty to review HEAD anyway.";
2302
+ var runGit = async (cwd, args) => {
2303
+ const child = spawn("git", args, {
2304
+ cwd,
2305
+ stdio: ["ignore", "pipe", "pipe"]
2306
+ });
2307
+ const stdout = [];
2308
+ child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
2309
+ const exitCode = await new Promise(
2310
+ (resolve) => child.on("exit", resolve)
2311
+ );
2312
+ return {
2313
+ ok: exitCode === 0,
2314
+ stdout: Buffer.concat(stdout).toString("utf8").trim()
2315
+ };
2316
+ };
2317
+ var normalizeGithubRepo = (value) => {
2318
+ const text = String(value ?? "").trim();
2319
+ if (!text) return void 0;
2320
+ const withoutGit = text.replace(/\.git$/i, "");
2321
+ const httpsMatch = withoutGit.match(/github\.com[:/]([^/\s]+)\/([^/\s]+)$/i);
2322
+ if (httpsMatch) {
2323
+ return `${httpsMatch[1]}/${httpsMatch[2]}`;
2324
+ }
2325
+ if (/^[^/\s]+\/[^/\s]+$/.test(withoutGit)) {
2326
+ return withoutGit;
2327
+ }
2328
+ return void 0;
2329
+ };
2330
+ var resolveGitMetadata = async (cwd, options) => {
2331
+ const status = await runGit(cwd, ["status", "--porcelain"]);
2332
+ const dirty = status.ok && status.stdout.length > 0;
2333
+ const remote = options.repo ? { ok: true, stdout: options.repo } : await runGit(cwd, ["remote", "get-url", "origin"]);
2334
+ const ref = options.ref ? options.ref : (await runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"])).stdout;
2335
+ const sha = options.commitSha ? options.commitSha : (await runGit(cwd, ["rev-parse", "HEAD"])).stdout;
2336
+ return {
2337
+ repo: normalizeGithubRepo(remote.stdout),
2338
+ ref: ref && ref !== "HEAD" ? ref : void 0,
2339
+ commitSha: sha || void 0,
2340
+ dirty
2341
+ };
2342
+ };
2343
+ var asRecord = (value) => value && typeof value === "object" ? value : {};
2344
+ var sourceOf = (project) => asRecord(project.iac_source ?? project.iacSource);
2345
+ var resolveProjectId = async ({
2346
+ baseUrl,
2347
+ token,
2348
+ requestedProjectId,
2349
+ repo,
2350
+ ref,
2351
+ sourceRoot
2352
+ }) => {
2353
+ if (requestedProjectId) {
2354
+ return requestedProjectId;
2355
+ }
2356
+ if (!repo) {
2357
+ throw new Error("Provide --repo or run inside a GitHub-backed Git repository.");
2358
+ }
2359
+ const projects = await fetchCloudEvalJson({
2360
+ baseUrl,
2361
+ authToken: token,
2362
+ path: "/projects"
2363
+ });
2364
+ const matches = projects.filter((project) => {
2365
+ const record = asRecord(project);
2366
+ const source = sourceOf(record);
2367
+ if (source.type !== "github") return false;
2368
+ if (source.repo_full_name !== repo) return false;
2369
+ if (sourceRoot !== void 0 && String(source.source_root ?? "") !== sourceRoot) {
2370
+ return false;
2371
+ }
2372
+ if (ref && source.ref && source.ref !== ref) {
2373
+ return false;
2374
+ }
2375
+ return true;
2376
+ });
2377
+ if (matches.length === 1) {
2378
+ return String(asRecord(matches[0]).id);
2379
+ }
2380
+ if (matches.length > 1) {
2381
+ throw new Error(
2382
+ `Multiple CloudEval projects match ${repo}. Pass --project to choose one.`
2383
+ );
2384
+ }
2385
+ throw new Error(
2386
+ `No CloudEval GitHub project matched ${repo}. Create one in CloudEval or pass --project.`
2387
+ );
2388
+ };
2389
+ var readConfigText = async (cwd, options) => {
2390
+ const configPath = options.config ?? path2.join(cwd, ".cloudeval", "config.yaml");
2391
+ try {
2392
+ return await fs2.readFile(path2.resolve(cwd, configPath), "utf8");
2393
+ } catch {
2394
+ return void 0;
2395
+ }
2396
+ };
2397
+ var parseGateConfig = (configText) => {
2398
+ if (!configText || !/^\s*ci\s*:/m.test(configText) || !/^\s*gates\s*:/m.test(configText)) {
2399
+ return void 0;
2400
+ }
2401
+ const numberValue2 = (key) => {
2402
+ const match = configText.match(new RegExp(`^\\s*${key}\\s*:\\s*([0-9]+(?:\\.[0-9]+)?)`, "m"));
2403
+ return match ? Number(match[1]) : void 0;
2404
+ };
2405
+ const booleanValue2 = (key) => {
2406
+ const match = configText.match(new RegExp(`^\\s*${key}\\s*:\\s*(true|false)`, "im"));
2407
+ return match ? match[1].toLowerCase() === "true" : void 0;
2408
+ };
2409
+ return {
2410
+ overallScoreMin: numberValue2("overall_score_min") ?? 80,
2411
+ failOnHighRisk: booleanValue2("fail_on_high_risk") ?? true,
2412
+ maxMonthlyCost: numberValue2("max_monthly_cost")
2413
+ };
2414
+ };
2415
+ var numberFrom = (...values) => {
2416
+ for (const value of values) {
2417
+ if (typeof value === "number" && Number.isFinite(value)) {
2418
+ return value;
2419
+ }
2420
+ if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) {
2421
+ return Number(value);
2422
+ }
2423
+ }
2424
+ return void 0;
2425
+ };
2426
+ var evaluateGate = ({
2427
+ configText,
2428
+ waf,
2429
+ cost
2430
+ }) => {
2431
+ const gateConfig = parseGateConfig(configText);
2432
+ const overallScore = numberFrom(
2433
+ waf?.parsed?.score?.overall,
2434
+ waf?.parsed?.overall_score,
2435
+ waf?.raw?.score
2436
+ );
2437
+ const highRisk = numberFrom(
2438
+ waf?.parsed?.counts?.highRisk,
2439
+ waf?.parsed?.counts?.high_count,
2440
+ waf?.parsed?.highRisk
2441
+ );
2442
+ const monthlyCost = numberFrom(
2443
+ cost?.parsed?.totalSpend?.amount,
2444
+ cost?.parsed?.total_spend?.amount,
2445
+ cost?.raw?.total
2446
+ );
2447
+ if (!gateConfig) {
2448
+ return {
2449
+ status: "warn",
2450
+ reason: "ci.gates is not configured in .cloudeval/config.yaml.",
2451
+ overallScore,
2452
+ highRisk,
2453
+ monthlyCost
2454
+ };
2455
+ }
2456
+ const failures = [];
2457
+ if (overallScore !== void 0 && overallScore < gateConfig.overallScoreMin) {
2458
+ failures.push(
2459
+ `overall score ${overallScore} is below ${gateConfig.overallScoreMin}`
2460
+ );
2461
+ }
2462
+ if (gateConfig.failOnHighRisk && highRisk !== void 0 && highRisk > 0) {
2463
+ failures.push(`${highRisk} high-risk architecture findings`);
2464
+ }
2465
+ if (gateConfig.maxMonthlyCost !== void 0 && monthlyCost !== void 0 && monthlyCost > gateConfig.maxMonthlyCost) {
2466
+ failures.push(`monthly cost ${monthlyCost} exceeds ${gateConfig.maxMonthlyCost}`);
2467
+ }
2468
+ return {
2469
+ status: failures.length ? "fail" : "pass",
2470
+ failures,
2471
+ thresholds: gateConfig,
2472
+ overallScore,
2473
+ highRisk,
2474
+ monthlyCost
2475
+ };
2476
+ };
2477
+ var safeFetch = async (input) => {
2478
+ try {
2479
+ return await fetchCloudEvalJson(input);
2480
+ } catch {
2481
+ return void 0;
2482
+ }
2483
+ };
2484
+ var parsePositiveInteger = (value, flagName, fallback) => {
2485
+ if (value === void 0 || value === "") {
2486
+ return fallback;
2487
+ }
2488
+ const parsed = Number(value);
2489
+ if (!Number.isFinite(parsed) || parsed <= 0) {
2490
+ throw new Error(`${flagName} must be a positive number of milliseconds.`);
2491
+ }
2492
+ return Math.floor(parsed);
2493
+ };
2494
+ var extractJobId2 = (value) => {
2495
+ const record = asRecord(value);
2496
+ const job = asRecord(record.job);
2497
+ const candidates = [
2498
+ record.job_id,
2499
+ record.jobId,
2500
+ record.id,
2501
+ job.job_id,
2502
+ job.jobId,
2503
+ job.id
2504
+ ];
2505
+ return candidates.find(
2506
+ (candidate) => typeof candidate === "string" && candidate.trim().length > 0
2507
+ );
2508
+ };
2509
+ var isTerminalJobStatus2 = (value) => {
2510
+ const status = String(asRecord(value).status ?? "").toUpperCase();
2511
+ return [
2512
+ "COMPLETED",
2513
+ "SUCCEEDED",
2514
+ "SUCCESS",
2515
+ "FAILED",
2516
+ "CANCELLED",
2517
+ "CANCELED",
2518
+ "ERROR"
2519
+ ].includes(status);
2520
+ };
2521
+ var isFailedJobStatus = (value) => {
2522
+ const status = String(asRecord(value).status ?? "").toUpperCase();
2523
+ return ["FAILED", "CANCELLED", "CANCELED", "ERROR"].includes(status);
2524
+ };
2525
+ var waitForJob = async ({
2526
+ baseUrl,
2527
+ token,
2528
+ userId,
2529
+ jobId,
2530
+ pollIntervalMs,
2531
+ waitTimeoutMs
2532
+ }) => {
2533
+ const startedAt = Date.now();
2534
+ let lastStatus;
2535
+ for (; ; ) {
2536
+ const query = userId ? `?user_id=${encodeURIComponent(userId)}` : "";
2537
+ lastStatus = await fetchCloudEvalJson({
2538
+ baseUrl,
2539
+ authToken: token,
2540
+ path: `/jobs/${encodeURIComponent(jobId)}${query}`
2541
+ });
2542
+ const status = String(lastStatus.status ?? "unknown");
2543
+ process.stderr.write(`github sync job ${jobId}: ${status}
2544
+ `);
2545
+ if (isTerminalJobStatus2(lastStatus)) {
2546
+ if (isFailedJobStatus(lastStatus)) {
2547
+ throw new Error(`GitHub sync job ${jobId} finished with status ${status}.`);
2548
+ }
2549
+ return lastStatus;
2550
+ }
2551
+ if (Date.now() - startedAt > waitTimeoutMs) {
2552
+ throw new Error(`Timed out waiting for GitHub sync job ${jobId}.`);
2553
+ }
2554
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2555
+ }
2556
+ };
2557
+ var fetchProjectById = async ({
2558
+ baseUrl,
2559
+ token,
2560
+ projectId
2561
+ }) => {
2562
+ const projects = await safeFetch({
2563
+ baseUrl,
2564
+ authToken: token,
2565
+ path: "/projects"
2566
+ });
2567
+ return projects?.map(asRecord).find((project) => project.id === projectId);
2568
+ };
2569
+ var buildAiSummaryPrompt = (data) => [
2570
+ "Write a concise CloudEval pull request review summary in Markdown.",
2571
+ "Focus on gate status, Well-Architected posture, cost posture, and security/operational risks.",
2572
+ "Keep it under 160 words. Do not invent facts not present below.",
2573
+ "",
2574
+ `Project: ${data.projectId}`,
2575
+ `Repository: ${data.repo ?? "unknown"}`,
2576
+ `Ref: ${data.ref ?? "unknown"}`,
2577
+ `Commit: ${data.commitSha ?? "unknown"}`,
2578
+ `Gate: ${String(data.gate?.status ?? "unknown").toUpperCase()}`,
2579
+ `Well-Architected score: ${data.gate?.overallScore ?? "unknown"}`,
2580
+ `High-risk findings: ${data.gate?.highRisk ?? "unknown"}`,
2581
+ `Monthly cost: ${data.gate?.monthlyCost ?? "unknown"}`,
2582
+ Array.isArray(data.gate?.failures) && data.gate.failures.length ? `Gate failures: ${data.gate.failures.join("; ")}` : "Gate failures: none reported"
2583
+ ].join("\n");
2584
+ var generateAiSummary = async ({
2585
+ baseUrl,
2586
+ token,
2587
+ user,
2588
+ project,
2589
+ model,
2590
+ data
2591
+ }) => {
2592
+ const core = await import("./dist-CFLR5FML.js");
2593
+ const threadId = `review-${data.projectId}-${Date.now()}`;
2594
+ let markdown = "";
2595
+ for await (const chunk of core.streamChat({
2596
+ baseUrl,
2597
+ authToken: token,
2598
+ message: buildAiSummaryPrompt(data),
2599
+ threadId,
2600
+ user: {
2601
+ id: String(project?.user_id ?? user?.id ?? "cli-user"),
2602
+ name: String(user?.full_name ?? user?.name ?? user?.email ?? "CloudEval CI")
2603
+ },
2604
+ project: project ? {
2605
+ id: String(project.id ?? data.projectId),
2606
+ name: String(project.name ?? data.projectId),
2607
+ user_id: typeof project.user_id === "string" ? project.user_id : void 0,
2608
+ cloud_provider: typeof project.cloud_provider === "string" ? project.cloud_provider : void 0,
2609
+ type: typeof project.type === "string" ? project.type : void 0,
2610
+ connection_ids: Array.isArray(project.connection_ids) ? project.connection_ids : void 0
2611
+ } : {
2612
+ id: String(data.projectId),
2613
+ name: String(data.projectId)
2614
+ },
2615
+ settings: {
2616
+ mode: "ask",
2617
+ ...model ? { model } : {}
2618
+ },
2619
+ completeAfterResponse: true,
2620
+ responseCompletionGraceMs: 250,
2621
+ streamIdleTimeoutMs: 3e4
2622
+ })) {
2623
+ const content = chunk?.content;
2624
+ if (chunk.type === "responding" && typeof content === "string") {
2625
+ markdown += content;
2626
+ }
2627
+ }
2628
+ return {
2629
+ enabled: true,
2630
+ mode: "ask",
2631
+ ...model ? { model } : {},
2632
+ markdown: markdown.trim(),
2633
+ threadId
2634
+ };
2635
+ };
2636
+ var buildMarkdownSummary = (data) => {
2637
+ const gateStatus = String(data.gate?.status ?? "unknown").toUpperCase();
2638
+ const score = data.gate?.overallScore ?? "unknown";
2639
+ const cost = data.gate?.monthlyCost ?? "unknown";
2640
+ const lines = [
2641
+ "### CloudEval review",
2642
+ "",
2643
+ `- **Project:** \`${data.projectId}\``,
2644
+ `- **Repository:** \`${data.repo ?? "unknown"}\``,
2645
+ `- **Ref:** \`${data.ref ?? "unknown"}\``,
2646
+ `- **Commit:** \`${String(data.commitSha ?? "unknown").slice(0, 12)}\``,
2647
+ `- **Gate:** ${gateStatus}`,
2648
+ `- **Well-Architected score:** ${score}`,
2649
+ `- **Monthly cost:** ${cost}`
2650
+ ];
2651
+ if (data.aiSummary?.markdown) {
2652
+ lines.push("", "## AI summary", "", data.aiSummary.markdown);
2653
+ }
2654
+ return lines.join("\n");
2655
+ };
2656
+ var registerReviewCommand = (program2, deps) => {
2657
+ const command = addAuthOptions(
2658
+ program2.command("review").description("Review the current GitHub-backed project from a pushed commit"),
2659
+ deps.defaultBaseUrl
2660
+ ).option("--project <id>", "CloudEval project id. If omitted, resolve by GitHub repo metadata.").option("--repo <owner/repo>", "GitHub repository. Defaults to git origin.").option("--ref <name>", "Git branch/ref. Defaults to current branch.").option("--commit-sha <sha>", "Commit SHA to sync/review. Defaults to local HEAD.").option("--source-root <path>", "GitHub source root used by the CloudEval project.").option("--config <path>", "Path to .cloudeval/config.yaml for gate thresholds.").option("--no-wait", "Submit GitHub sync and return without waiting for analysis.").option("--wait-timeout <ms>", "Maximum time to wait for GitHub sync.", "900000").option("--poll-interval <ms>", "Polling interval while waiting for GitHub sync.", "5000").option("--no-ai-summary", "Skip the AI-written review summary.").option("--ignore-dirty", "Review HEAD even if the local working tree has uncommitted changes.", false).option("--output <dir>", "Write review.json and review.md into a directory.").option("--quiet", "Accepted for CI parity; review output stays machine-readable.", false).option("--progress <mode>", "Accepted for CI parity; review does not stream progress.", "none").option("--model <model>", "Accepted for CI parity with ask/agent modes.").option("--format <format>", "Output format: text, json, ndjson, markdown", "text");
2661
+ command.action(async (options, actionCommand) => {
2662
+ try {
2663
+ const cwd = process.cwd();
2664
+ const git = await resolveGitMetadata(cwd, options);
2665
+ if (git.dirty && !options.ignoreDirty) {
2666
+ throw new Error(DIRTY_REVIEW_MESSAGE);
2667
+ }
2668
+ const context = await resolveAuthContext(options, actionCommand, deps);
2669
+ const repo = normalizeGithubRepo(options.repo) ?? git.repo;
2670
+ const ref = options.ref ?? git.ref;
2671
+ const commitSha = options.commitSha ?? git.commitSha;
2672
+ const sourceRoot = options.sourceRoot;
2673
+ const projectId = await resolveProjectId({
2674
+ baseUrl: context.baseUrl,
2675
+ token: context.token,
2676
+ requestedProjectId: options.project,
2677
+ repo,
2678
+ ref,
2679
+ sourceRoot
2680
+ });
2681
+ const sync = await fetchCloudEvalJson({
2682
+ baseUrl: context.baseUrl,
2683
+ authToken: context.token,
2684
+ path: `/projects/${projectId}/github/sync`,
2685
+ method: "POST",
2686
+ body: commitSha ? { commit_sha: commitSha } : {},
2687
+ idempotencyKey: `cloudeval-review-${projectId}-${commitSha ?? "head"}`
2688
+ });
2689
+ const finalStatus = options.wait === false ? void 0 : extractJobId2(sync) ? await waitForJob({
2690
+ baseUrl: context.baseUrl,
2691
+ token: context.token,
2692
+ userId: context.user?.id,
2693
+ jobId: extractJobId2(sync),
2694
+ pollIntervalMs: parsePositiveInteger(
2695
+ options.pollInterval,
2696
+ "--poll-interval",
2697
+ 5e3
2698
+ ),
2699
+ waitTimeoutMs: parsePositiveInteger(
2700
+ options.waitTimeout,
2701
+ "--wait-timeout",
2702
+ 9e5
2703
+ )
2704
+ }) : void 0;
2705
+ const [cost, waf, configText] = await Promise.all([
2706
+ safeFetch({
2707
+ baseUrl: context.baseUrl,
2708
+ authToken: context.token,
2709
+ path: `/cost-reports/${projectId}/full`
2710
+ }),
2711
+ safeFetch({
2712
+ baseUrl: context.baseUrl,
2713
+ authToken: context.token,
2714
+ path: `/well-architected-reports/${projectId}/full`
2715
+ }),
2716
+ readConfigText(cwd, options)
2717
+ ]);
2718
+ const project = await fetchProjectById({
2719
+ baseUrl: context.baseUrl,
2720
+ token: context.token,
2721
+ projectId
2722
+ });
2723
+ const data = {
2724
+ projectId,
2725
+ repo,
2726
+ ref,
2727
+ commitSha,
2728
+ sourceRoot,
2729
+ sync: finalStatus ? { ...asRecord(sync), finalStatus } : sync,
2730
+ reports: {
2731
+ cost,
2732
+ waf
2733
+ },
2734
+ gate: evaluateGate({ configText, waf, cost })
2735
+ };
2736
+ if (options.aiSummary !== false) {
2737
+ try {
2738
+ data.aiSummary = await generateAiSummary({
2739
+ baseUrl: context.baseUrl,
2740
+ token: context.token,
2741
+ user: context.user,
2742
+ project,
2743
+ model: options.model,
2744
+ data
2745
+ });
2746
+ } catch (error) {
2747
+ data.aiSummary = {
2748
+ enabled: true,
2749
+ status: "failed",
2750
+ error: error?.message ?? "AI summary failed"
2751
+ };
2752
+ }
2753
+ } else {
2754
+ data.aiSummary = { enabled: false };
2755
+ }
2756
+ const summaryMarkdown = buildMarkdownSummary(data);
2757
+ const filesWritten = [];
2758
+ if (options.output) {
2759
+ const outputDir = path2.resolve(options.output);
2760
+ await fs2.mkdir(outputDir, { recursive: true });
2761
+ const jsonPath = path2.join(outputDir, "review.json");
2762
+ const markdownPath = path2.join(outputDir, "review.md");
2763
+ await fs2.writeFile(jsonPath, JSON.stringify(data, null, 2), "utf8");
2764
+ await fs2.writeFile(markdownPath, summaryMarkdown, "utf8");
2765
+ filesWritten.push(jsonPath, markdownPath);
2766
+ }
2767
+ await writeFormattedOutput({
2768
+ command: "review",
2769
+ data: { ...data, summaryMarkdown },
2770
+ format: options.format,
2771
+ filesWritten
2772
+ });
2773
+ if (data.gate.status === "fail") {
2774
+ process.exit(1);
2775
+ }
2776
+ process.exit(0);
2777
+ } catch (error) {
2778
+ console.error(error?.message ?? "Review failed");
2779
+ process.exit(1);
2780
+ }
2781
+ });
2782
+ };
2783
+
2220
2784
  // src/recipesCommand.ts
2221
2785
  import { randomUUID } from "crypto";
2222
- import fs2 from "fs/promises";
2786
+ import fs3 from "fs/promises";
2223
2787
 
2224
2788
  // src/askProgress.ts
2225
2789
  var ASK_PROGRESS_MODES = /* @__PURE__ */ new Set(["auto", "stderr", "ndjson", "none"]);
@@ -3455,7 +4019,7 @@ var writeRecipeList = async (options) => {
3455
4019
  if (format === "table" || format === "text") {
3456
4020
  const text = renderRecipesTable();
3457
4021
  if (options.output) {
3458
- await fs2.writeFile(options.output, text, "utf8");
4022
+ await fs3.writeFile(options.output, text, "utf8");
3459
4023
  return;
3460
4024
  }
3461
4025
  process.stdout.write(text);
@@ -3464,7 +4028,7 @@ var writeRecipeList = async (options) => {
3464
4028
  if (format === "markdown") {
3465
4029
  const text = renderRecipesMarkdown();
3466
4030
  if (options.output) {
3467
- await fs2.writeFile(options.output, text, "utf8");
4031
+ await fs3.writeFile(options.output, text, "utf8");
3468
4032
  return;
3469
4033
  }
3470
4034
  process.stdout.write(text);
@@ -3482,7 +4046,7 @@ var writeRecipeShow = async (recipe, options) => {
3482
4046
  if (format === "markdown" || format === "text") {
3483
4047
  const text = renderRecipeMarkdown(recipe);
3484
4048
  if (options.output) {
3485
- await fs2.writeFile(options.output, text, "utf8");
4049
+ await fs3.writeFile(options.output, text, "utf8");
3486
4050
  return;
3487
4051
  }
3488
4052
  process.stdout.write(text);
@@ -3677,15 +4241,15 @@ var registerRecipesCommand = (program2, deps) => {
3677
4241
  };
3678
4242
 
3679
4243
  // src/skillsCommand.ts
3680
- import fs4 from "fs/promises";
4244
+ import fs5 from "fs/promises";
3681
4245
 
3682
4246
  // src/skills/catalog.ts
3683
- import fs3 from "fs/promises";
4247
+ import fs4 from "fs/promises";
3684
4248
  import fsSync from "fs";
3685
- import path2 from "path";
4249
+ import path3 from "path";
3686
4250
  import { fileURLToPath as fileURLToPath2 } from "url";
3687
- var repoRoot = path2.resolve(fileURLToPath2(new URL("../../../../", import.meta.url)));
3688
- var defaultSkillsPath = path2.join(repoRoot, "skills");
4251
+ var repoRoot = path3.resolve(fileURLToPath2(new URL("../../../../", import.meta.url)));
4252
+ var defaultSkillsPath = path3.join(repoRoot, "skills");
3689
4253
  var requiredSkillSections = [
3690
4254
  "## WHEN",
3691
4255
  "## DO NOT USE FOR",
@@ -3773,7 +4337,7 @@ var normalizeSkillId = (id) => {
3773
4337
  return metadataById.has(prefixed) ? prefixed : normalized;
3774
4338
  };
3775
4339
  var getSkillsPath = () => process.env.CLOUDEVAL_SKILLS_PATH ?? defaultSkillsPath;
3776
- var skillFilePath = (id) => path2.join(getSkillsPath(), id, "SKILL.md");
4340
+ var skillFilePath = (id) => path3.join(getSkillsPath(), id, "SKILL.md");
3777
4341
  var fileExists = (filePath) => {
3778
4342
  try {
3779
4343
  return fsSync.statSync(filePath).isFile();
@@ -3785,7 +4349,7 @@ var readSkillFile = async (id) => {
3785
4349
  const filePath = skillFilePath(id);
3786
4350
  if (fileExists(filePath)) {
3787
4351
  return {
3788
- content: await fs3.readFile(filePath, "utf8"),
4352
+ content: await fs4.readFile(filePath, "utf8"),
3789
4353
  path: filePath,
3790
4354
  source: "filesystem"
3791
4355
  };
@@ -3982,7 +4546,7 @@ var writeSkillList = async (options) => {
3982
4546
  if (format === "table" || format === "text") {
3983
4547
  const text = renderSkillsTable(skills);
3984
4548
  if (options.output) {
3985
- await fs4.writeFile(options.output, text, "utf8");
4549
+ await fs5.writeFile(options.output, text, "utf8");
3986
4550
  return;
3987
4551
  }
3988
4552
  process.stdout.write(text);
@@ -3991,7 +4555,7 @@ var writeSkillList = async (options) => {
3991
4555
  if (format === "markdown") {
3992
4556
  const text = renderSkillsMarkdown(skills);
3993
4557
  if (options.output) {
3994
- await fs4.writeFile(options.output, text, "utf8");
4558
+ await fs5.writeFile(options.output, text, "utf8");
3995
4559
  return;
3996
4560
  }
3997
4561
  process.stdout.write(text);
@@ -4014,7 +4578,7 @@ var writeSkillShow = async (id, options) => {
4014
4578
  const format = options.format ?? "markdown";
4015
4579
  if (format === "markdown" || format === "text") {
4016
4580
  if (options.output) {
4017
- await fs4.writeFile(options.output, skill.content, "utf8");
4581
+ await fs5.writeFile(options.output, skill.content, "utf8");
4018
4582
  return;
4019
4583
  }
4020
4584
  process.stdout.write(skill.content);
@@ -4069,7 +4633,7 @@ var writeSkillDoctor = async (options) => {
4069
4633
  ""
4070
4634
  ].filter(Boolean).join("\n");
4071
4635
  if (options.output) {
4072
- await fs4.writeFile(options.output, `${text}
4636
+ await fs5.writeFile(options.output, `${text}
4073
4637
  `, "utf8");
4074
4638
  return;
4075
4639
  }
@@ -4101,7 +4665,7 @@ var registerSkillsCommand = (program2) => {
4101
4665
  const text = `${skillsPath}
4102
4666
  `;
4103
4667
  if (options.output) {
4104
- await fs4.writeFile(options.output, text, "utf8");
4668
+ await fs5.writeFile(options.output, text, "utf8");
4105
4669
  return;
4106
4670
  }
4107
4671
  process.stdout.write(text);
@@ -4274,10 +4838,10 @@ var registerOpenCommand = (program2, deps) => {
4274
4838
  };
4275
4839
 
4276
4840
  // src/projectsCommand.ts
4277
- import path3 from "path";
4278
- import fs5 from "fs/promises";
4841
+ import path4 from "path";
4842
+ import fs6 from "fs/promises";
4279
4843
  import os from "os";
4280
- import { spawn } from "child_process";
4844
+ import { spawn as spawn2 } from "child_process";
4281
4845
 
4282
4846
  // src/projectDiagramImage.ts
4283
4847
  var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
@@ -4434,50 +4998,6 @@ var downloadProjectDiagramImage = async (input) => {
4434
4998
  };
4435
4999
  };
4436
5000
 
4437
- // src/apiClient.ts
4438
- var responseErrorMessage = async (response) => {
4439
- const text = await response.text();
4440
- if (!text) {
4441
- return `request failed with status ${response.status}`;
4442
- }
4443
- try {
4444
- const payload = JSON.parse(text);
4445
- const detail = payload?.detail ?? payload?.message ?? payload?.error;
4446
- if (typeof detail === "string") {
4447
- return detail;
4448
- }
4449
- if (detail) {
4450
- return JSON.stringify(detail);
4451
- }
4452
- } catch {
4453
- }
4454
- return text;
4455
- };
4456
- var fetchCloudEvalJson = async ({
4457
- baseUrl,
4458
- authToken,
4459
- path: path10,
4460
- method = "GET",
4461
- query = {},
4462
- body
4463
- }) => {
4464
- const url = new URL(`${normalizeApiBase(baseUrl)}${path10}`);
4465
- for (const [key, value] of Object.entries(query)) {
4466
- if (value !== void 0 && value !== null && value !== "") {
4467
- url.searchParams.set(key, String(value));
4468
- }
4469
- }
4470
- const response = await fetch(url, {
4471
- method,
4472
- headers: getCLIHeaders(authToken),
4473
- ...body === void 0 ? {} : { body: JSON.stringify(body) }
4474
- });
4475
- if (!response.ok) {
4476
- throw new Error(await responseErrorMessage(response));
4477
- }
4478
- return await response.json();
4479
- };
4480
-
4481
5001
  // src/graphClient.ts
4482
5002
  var normalizeGraphInsightFocus = (focus) => {
4483
5003
  const normalized = String(focus ?? "overview").trim().toLowerCase();
@@ -4637,7 +5157,7 @@ var writeProjectListOutput = async ({
4637
5157
  });
4638
5158
  }
4639
5159
  if (options.output) {
4640
- await fs5.writeFile(options.output, text, "utf8");
5160
+ await fs6.writeFile(options.output, text, "utf8");
4641
5161
  return;
4642
5162
  }
4643
5163
  process.stdout.write(text);
@@ -4646,10 +5166,10 @@ var fileBlob = async (filePath) => {
4646
5166
  if (!filePath) {
4647
5167
  return void 0;
4648
5168
  }
4649
- const bytes = await fs5.readFile(filePath);
5169
+ const bytes = await fs6.readFile(filePath);
4650
5170
  return {
4651
5171
  blob: new Blob([bytes], { type: "application/json" }),
4652
- name: path3.basename(filePath)
5172
+ name: path4.basename(filePath)
4653
5173
  };
4654
5174
  };
4655
5175
  var normalizeWorkspacePath = (value) => value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/^\.\//, "").split("/").filter((part) => part && part !== ".").join("/");
@@ -4687,7 +5207,7 @@ var generateWorkspaceConfig = (entry, parameters, sourceEntry) => {
4687
5207
  ].filter((line) => line.length > 0).join("\n");
4688
5208
  };
4689
5209
  var runCommand = (command, args) => new Promise((resolve, reject) => {
4690
- const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
5210
+ const child = spawn2(command, args, { stdio: ["ignore", "pipe", "pipe"] });
4691
5211
  const stdout = [];
4692
5212
  const stderr = [];
4693
5213
  child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
@@ -4711,9 +5231,9 @@ var runCommand = (command, args) => new Promise((resolve, reject) => {
4711
5231
  });
4712
5232
  var isBicepPath = (filePath) => /\.bicep$/i.test(filePath);
4713
5233
  var compiledBicepPathFor = (entry) => {
4714
- const parsed = path3.posix.parse(entry);
5234
+ const parsed = path4.posix.parse(entry);
4715
5235
  return normalizeWorkspacePath(
4716
- path3.posix.join(
5236
+ path4.posix.join(
4717
5237
  ".cloudeval/template-cache/compiled",
4718
5238
  parsed.dir,
4719
5239
  `${parsed.name}.json`
@@ -4721,18 +5241,18 @@ var compiledBicepPathFor = (entry) => {
4721
5241
  );
4722
5242
  };
4723
5243
  var compileBicepEntry = async (root, entry) => {
4724
- const tempDir = await fs5.mkdtemp(path3.join(os.tmpdir(), "cloudeval-bicep-"));
4725
- const outputPath = path3.join(tempDir, "compiled.json");
5244
+ const tempDir = await fs6.mkdtemp(path4.join(os.tmpdir(), "cloudeval-bicep-"));
5245
+ const outputPath = path4.join(tempDir, "compiled.json");
4726
5246
  try {
4727
5247
  await runCommand("az", [
4728
5248
  "bicep",
4729
5249
  "build",
4730
5250
  "--file",
4731
- path3.join(root, entry),
5251
+ path4.join(root, entry),
4732
5252
  "--outfile",
4733
5253
  outputPath
4734
5254
  ]);
4735
- const bytes = await fs5.readFile(outputPath);
5255
+ const bytes = await fs6.readFile(outputPath);
4736
5256
  return {
4737
5257
  path: compiledBicepPathFor(entry),
4738
5258
  blob: new Blob([bytes], { type: "application/json" })
@@ -4746,16 +5266,16 @@ var compileBicepEntry = async (root, entry) => {
4746
5266
  }
4747
5267
  throw new Error(`Failed to compile Bicep workspace entry '${entry}': ${message}`);
4748
5268
  } finally {
4749
- await fs5.rm(tempDir, { recursive: true, force: true });
5269
+ await fs6.rm(tempDir, { recursive: true, force: true });
4750
5270
  }
4751
5271
  };
4752
5272
  var collectWorkspacePaths = async (root) => {
4753
5273
  const paths = [];
4754
5274
  const visit = async (directory) => {
4755
- const entries = await fs5.readdir(directory, { withFileTypes: true });
5275
+ const entries = await fs6.readdir(directory, { withFileTypes: true });
4756
5276
  for (const entry of entries) {
4757
- const absolute = path3.join(directory, entry.name);
4758
- const relative = normalizeWorkspacePath(path3.relative(root, absolute));
5277
+ const absolute = path4.join(directory, entry.name);
5278
+ const relative = normalizeWorkspacePath(path4.relative(root, absolute));
4759
5279
  if (!relative || isIgnoredWorkspacePath(relative)) {
4760
5280
  continue;
4761
5281
  }
@@ -4822,8 +5342,8 @@ var resolveWorkspaceParameters = (paths, explicitParameters, config) => {
4822
5342
  return findFirstPath(paths, ["azuredeploy.parameters.json", "parameters.json"]);
4823
5343
  };
4824
5344
  var collectWorkspaceFiles = async (workspaceDir, options) => {
4825
- const root = path3.resolve(workspaceDir);
4826
- const stat = await fs5.stat(root).catch(() => void 0);
5345
+ const root = path4.resolve(workspaceDir);
5346
+ const stat = await fs6.stat(root).catch(() => void 0);
4827
5347
  if (!stat?.isDirectory()) {
4828
5348
  throw new Error(`--workspace-dir '${workspaceDir}' is not a directory.`);
4829
5349
  }
@@ -4831,7 +5351,7 @@ var collectWorkspaceFiles = async (workspaceDir, options) => {
4831
5351
  const existingConfigPath = paths.find(
4832
5352
  (filePath) => filePath.toLowerCase() === ".cloudeval/config.yaml"
4833
5353
  );
4834
- const config = existingConfigPath ? readWorkspaceConfig(await fs5.readFile(path3.join(root, existingConfigPath), "utf8")) : void 0;
5354
+ const config = existingConfigPath ? readWorkspaceConfig(await fs6.readFile(path4.join(root, existingConfigPath), "utf8")) : void 0;
4835
5355
  const entry = detectWorkspaceEntry(paths, options.workspaceEntry, config);
4836
5356
  const parameters = resolveWorkspaceParameters(paths, options.workspaceParameters, config);
4837
5357
  const compiledEntry = isBicepPath(entry) ? await compileBicepEntry(root, entry) : void 0;
@@ -4853,7 +5373,7 @@ var collectWorkspaceFiles = async (workspaceDir, options) => {
4853
5373
  });
4854
5374
  continue;
4855
5375
  }
4856
- const bytes = await fs5.readFile(path3.join(root, relativePath));
5376
+ const bytes = await fs6.readFile(path4.join(root, relativePath));
4857
5377
  files.push({
4858
5378
  path: relativePath,
4859
5379
  blob: new Blob([bytes], {
@@ -4915,9 +5435,9 @@ var appendOptionValue = (value, previous = []) => [
4915
5435
  value
4916
5436
  ];
4917
5437
  var writeDiagramImageHeaders = async (outputPath, headers) => {
4918
- await fs5.mkdir(path3.dirname(outputPath), { recursive: true });
5438
+ await fs6.mkdir(path4.dirname(outputPath), { recursive: true });
4919
5439
  const text = Object.entries(headers).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}: ${value}`).join("\n");
4920
- await fs5.writeFile(outputPath, `${text}
5440
+ await fs6.writeFile(outputPath, `${text}
4921
5441
  `, "utf8");
4922
5442
  };
4923
5443
  var listProjectsForContext = async (core, context) => {
@@ -5080,10 +5600,10 @@ var configureDiagramExportCommand = (command, deps) => addAuthOptions(command, d
5080
5600
  publicGraph,
5081
5601
  syncVersion: options.syncVersion
5082
5602
  });
5083
- const outputPath = path3.resolve(options.output);
5084
- const headersOutputPath = options.headersOutput ? path3.resolve(options.headersOutput) : void 0;
5085
- await fs5.mkdir(path3.dirname(outputPath), { recursive: true });
5086
- await fs5.writeFile(outputPath, result.bytes);
5603
+ const outputPath = path4.resolve(options.output);
5604
+ const headersOutputPath = options.headersOutput ? path4.resolve(options.headersOutput) : void 0;
5605
+ await fs6.mkdir(path4.dirname(outputPath), { recursive: true });
5606
+ await fs6.writeFile(outputPath, result.bytes);
5087
5607
  const filesWritten = [outputPath];
5088
5608
  if (headersOutputPath) {
5089
5609
  await writeDiagramImageHeaders(headersOutputPath, result.headers);
@@ -5217,7 +5737,7 @@ var registerProjectsCommand = (program2, deps) => {
5217
5737
  const parameters = await fileBlob(options.parametersFile);
5218
5738
  const workspace = options.workspaceDir ? await collectWorkspaceFiles(options.workspaceDir, options) : void 0;
5219
5739
  const cloudSync = resolveAzureCloudSyncInput(options);
5220
- const inferredName = options.name || (options.workspaceDir ? path3.basename(path3.resolve(options.workspaceDir)) : void 0) || (cloudSync ? "Cloud sync" : void 0) || (options.templateFile ? path3.basename(options.templateFile, path3.extname(options.templateFile)) : void 0);
5740
+ const inferredName = options.name || (options.workspaceDir ? path4.basename(path4.resolve(options.workspaceDir)) : void 0) || (cloudSync ? "Cloud sync" : void 0) || (options.templateFile ? path4.basename(options.templateFile, path4.extname(options.templateFile)) : void 0);
5221
5741
  const result = await core.createQuickProject({
5222
5742
  baseUrl: context.baseUrl,
5223
5743
  authToken: context.token,
@@ -5321,8 +5841,8 @@ var writeConnectionsListOutput = async ({
5321
5841
  if (format === "text") {
5322
5842
  const text = renderConnectionsListText(data);
5323
5843
  if (options.output) {
5324
- const fs13 = await import("fs/promises");
5325
- await fs13.writeFile(options.output, text, "utf8");
5844
+ const fs14 = await import("fs/promises");
5845
+ await fs14.writeFile(options.output, text, "utf8");
5326
5846
  return;
5327
5847
  }
5328
5848
  process.stdout.write(text);
@@ -5656,8 +6176,8 @@ var write = async (command, data, options, frontendUrl) => {
5656
6176
  const text = renderBillingText(command, data);
5657
6177
  if (text) {
5658
6178
  if (options.output) {
5659
- const fs13 = await import("fs/promises");
5660
- await fs13.writeFile(options.output, text, "utf8");
6179
+ const fs14 = await import("fs/promises");
6180
+ await fs14.writeFile(options.output, text, "utf8");
5661
6181
  return;
5662
6182
  }
5663
6183
  process.stdout.write(text);
@@ -5926,14 +6446,14 @@ var registerBillingCommands = (program2, deps) => {
5926
6446
  };
5927
6447
 
5928
6448
  // src/mcpCommand.ts
5929
- import fs8 from "fs/promises";
5930
- import path5 from "path";
6449
+ import fs9 from "fs/promises";
6450
+ import path6 from "path";
5931
6451
  import { randomUUID as randomUUID2 } from "crypto";
5932
6452
 
5933
6453
  // src/mcpSetupCommand.ts
5934
- import fs6 from "fs/promises";
6454
+ import fs7 from "fs/promises";
5935
6455
  import os2 from "os";
5936
- import path4 from "path";
6456
+ import path5 from "path";
5937
6457
  var MCP_SETUP_CLIENTS = ["codex", "claude", "cursor", "vscode", "generic"];
5938
6458
  var CLIENTS = new Set(MCP_SETUP_CLIENTS);
5939
6459
  var TOOLSETS = /* @__PURE__ */ new Set([
@@ -5959,7 +6479,7 @@ var normalizeMcpSetupToolset = (value) => {
5959
6479
  };
5960
6480
  var defaultConfigPath = (client) => {
5961
6481
  if (client === "claude") {
5962
- return path4.join(
6482
+ return path5.join(
5963
6483
  os2.homedir(),
5964
6484
  "Library",
5965
6485
  "Application Support",
@@ -5968,10 +6488,10 @@ var defaultConfigPath = (client) => {
5968
6488
  );
5969
6489
  }
5970
6490
  if (client === "cursor") {
5971
- return path4.join(os2.homedir(), ".cursor", "mcp.json");
6491
+ return path5.join(os2.homedir(), ".cursor", "mcp.json");
5972
6492
  }
5973
6493
  if (client === "vscode") {
5974
- return path4.join(process.cwd(), ".vscode", "mcp.json");
6494
+ return path5.join(process.cwd(), ".vscode", "mcp.json");
5975
6495
  }
5976
6496
  return void 0;
5977
6497
  };
@@ -6081,7 +6601,7 @@ var formatMcpClientSetupText = (setup, options = {}) => {
6081
6601
  };
6082
6602
  var readJsonObject = async (filePath) => {
6083
6603
  try {
6084
- const raw = await fs6.readFile(filePath, "utf8");
6604
+ const raw = await fs7.readFile(filePath, "utf8");
6085
6605
  const parsed = JSON.parse(raw);
6086
6606
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
6087
6607
  } catch (error) {
@@ -6106,8 +6626,8 @@ var writeMcpClientConfig = async (setup) => {
6106
6626
  cloudeval: nextServer
6107
6627
  }
6108
6628
  };
6109
- await fs6.mkdir(path4.dirname(setup.configPath), { recursive: true });
6110
- await fs6.writeFile(setup.configPath, `${JSON.stringify(next, null, 2)}
6629
+ await fs7.mkdir(path5.dirname(setup.configPath), { recursive: true });
6630
+ await fs7.writeFile(setup.configPath, `${JSON.stringify(next, null, 2)}
6111
6631
  `, {
6112
6632
  encoding: "utf8",
6113
6633
  mode: 384
@@ -6116,9 +6636,9 @@ var writeMcpClientConfig = async (setup) => {
6116
6636
  };
6117
6637
 
6118
6638
  // src/templateValidationClient.ts
6119
- import fs7 from "fs/promises";
6639
+ import fs8 from "fs/promises";
6120
6640
  var readJsonFile = async (filePath) => {
6121
- const text = await fs7.readFile(filePath, "utf8");
6641
+ const text = await fs8.readFile(filePath, "utf8");
6122
6642
  try {
6123
6643
  return JSON.parse(text);
6124
6644
  } catch (error) {
@@ -6169,7 +6689,7 @@ var stringField = (value, field) => {
6169
6689
  const raw = value?.[field];
6170
6690
  return typeof raw === "string" && raw.trim() ? raw : void 0;
6171
6691
  };
6172
- var extractJobId2 = (value) => {
6692
+ var extractJobId3 = (value) => {
6173
6693
  const record = recordValue(value);
6174
6694
  const job = recordValue(record?.job);
6175
6695
  const data = recordValue(record?.data);
@@ -6177,7 +6697,7 @@ var extractJobId2 = (value) => {
6177
6697
  return stringField(job, "job_id") ?? stringField(job, "jobId") ?? stringField(record, "job_id") ?? stringField(record, "jobId") ?? stringField(dataJob, "job_id") ?? stringField(dataJob, "jobId");
6178
6698
  };
6179
6699
  var normalizedStatus = (value) => String(recordValue(value)?.status ?? "").trim().toLowerCase();
6180
- var isTerminalJobStatus2 = (value) => [
6700
+ var isTerminalJobStatus3 = (value) => [
6181
6701
  "completed",
6182
6702
  "succeeded",
6183
6703
  "failed",
@@ -6326,7 +6846,7 @@ var getTemplateValidationJobResult = async (input) => fetchCloudEvalJson({
6326
6846
  query: { user_id: input.userId }
6327
6847
  });
6328
6848
  var waitForTemplateValidationResult = async (input) => {
6329
- const jobId = extractJobId2(input.submitted);
6849
+ const jobId = extractJobId3(input.submitted);
6330
6850
  if (!jobId) {
6331
6851
  return input.submitted;
6332
6852
  }
@@ -6336,7 +6856,7 @@ var waitForTemplateValidationResult = async (input) => {
6336
6856
  let status;
6337
6857
  for (; ; ) {
6338
6858
  status = await getTemplateValidationJobStatus({ ...input, jobId });
6339
- if (isTerminalJobStatus2(status)) {
6859
+ if (isTerminalJobStatus3(status)) {
6340
6860
  break;
6341
6861
  }
6342
6862
  if (Date.now() >= deadline) {
@@ -6598,9 +7118,15 @@ var createApplicationInsightsClient = async (connectionString) => {
6598
7118
  const client = new appInsights.TelemetryClient(connectionString, {
6599
7119
  useGlobalProviders: false
6600
7120
  });
6601
- client.setUseDiskRetryCaching(false);
7121
+ disableDiskRetryCaching(client);
6602
7122
  return client;
6603
7123
  };
7124
+ var disableDiskRetryCaching = (client) => {
7125
+ try {
7126
+ client.setUseDiskRetryCaching?.(false);
7127
+ } catch {
7128
+ }
7129
+ };
6604
7130
  var flushClient = async (client) => {
6605
7131
  try {
6606
7132
  const flush = client.flush;
@@ -8501,12 +9027,12 @@ var pickReportDownloadPayload2 = (value, view) => {
8501
9027
  }
8502
9028
  return value;
8503
9029
  };
8504
- var extractJobId3 = (value) => {
9030
+ var extractJobId4 = (value) => {
8505
9031
  if (!value || typeof value !== "object") return void 0;
8506
9032
  const record = value;
8507
9033
  return record.job_id ?? record.id ?? record.job?.job_id ?? record.job?.id ?? record.data?.job_id ?? record.data?.job?.job_id;
8508
9034
  };
8509
- var isTerminalJobStatus3 = (value) => {
9035
+ var isTerminalJobStatus4 = (value) => {
8510
9036
  if (!value || typeof value !== "object") return true;
8511
9037
  const status = String(
8512
9038
  value.status ?? ""
@@ -8522,9 +9048,9 @@ var isTerminalJobStatus3 = (value) => {
8522
9048
  };
8523
9049
  var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8524
9050
  var writeHeaderFile = async (outputPath, headers) => {
8525
- await fs8.mkdir(path5.dirname(outputPath), { recursive: true });
9051
+ await fs9.mkdir(path6.dirname(outputPath), { recursive: true });
8526
9052
  const text = Object.entries(headers).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}: ${value}`).join("\n");
8527
- await fs8.writeFile(outputPath, `${text}
9053
+ await fs9.writeFile(outputPath, `${text}
8528
9054
  `, "utf8");
8529
9055
  };
8530
9056
  var resolveInvocationConfig = async (serverOptions, args) => {
@@ -8581,15 +9107,8 @@ var resolveProject2 = async (config, args, auth) => {
8581
9107
  const requestedProjectId = stringValue(args.projectId) ?? config.defaultProjectId;
8582
9108
  const userId = auth.user?.id;
8583
9109
  if (!userId) {
8584
- if (requestedProjectId) {
8585
- return {
8586
- id: requestedProjectId,
8587
- name: "Selected Project",
8588
- cloud_provider: "azure"
8589
- };
8590
- }
8591
9110
  throw new Error(
8592
- "Could not determine the authenticated user. Provide projectId."
9111
+ "Could not determine the authenticated user. Run `cloudeval login` and retry."
8593
9112
  );
8594
9113
  }
8595
9114
  const projects = await auth.core.getProjects(
@@ -8598,12 +9117,15 @@ var resolveProject2 = async (config, args, auth) => {
8598
9117
  userId
8599
9118
  );
8600
9119
  if (requestedProjectId) {
8601
- return projects.find((project) => project.id === requestedProjectId) ?? {
8602
- id: requestedProjectId,
8603
- name: "Selected Project",
8604
- user_id: userId,
8605
- cloud_provider: "azure"
8606
- };
9120
+ const match = projects.find(
9121
+ (project) => project.id === requestedProjectId
9122
+ );
9123
+ if (!match) {
9124
+ throw new Error(
9125
+ `Project ${requestedProjectId} was not found for authenticated user ${userId}. Run \`cloudeval projects list\` to choose a visible project.`
9126
+ );
9127
+ }
9128
+ return match;
8607
9129
  }
8608
9130
  const selected = projects.find((project) => project.name === "Playground") ?? projects[0];
8609
9131
  if (selected) {
@@ -8687,7 +9209,7 @@ var assertModelAvailable = async (config, token) => {
8687
9209
  }
8688
9210
  };
8689
9211
  var waitForReportJobs2 = async (input) => {
8690
- const jobIds = input.submitted.map(extractJobId3).filter(Boolean);
9212
+ const jobIds = input.submitted.map(extractJobId4).filter(Boolean);
8691
9213
  if (!jobIds.length) {
8692
9214
  return input.submitted;
8693
9215
  }
@@ -8701,7 +9223,7 @@ var waitForReportJobs2 = async (input) => {
8701
9223
  userId: input.userId,
8702
9224
  jobId
8703
9225
  });
8704
- if (isTerminalJobStatus3(lastStatus)) {
9226
+ if (isTerminalJobStatus4(lastStatus)) {
8705
9227
  break;
8706
9228
  }
8707
9229
  await sleep3(input.pollIntervalMs);
@@ -8770,13 +9292,13 @@ var downloadReports = async (config, args, auth) => {
8770
9292
  const filesWritten = [];
8771
9293
  if (outputPath) {
8772
9294
  if (reportTypes.length > 1) {
8773
- const stat = await fs8.stat(outputPath).catch(() => void 0);
8774
- const outputIsDirectory = stat?.isDirectory() || !path5.extname(outputPath);
9295
+ const stat = await fs9.stat(outputPath).catch(() => void 0);
9296
+ const outputIsDirectory = stat?.isDirectory() || !path6.extname(outputPath);
8775
9297
  if (outputIsDirectory) {
8776
- await fs8.mkdir(outputPath, { recursive: true });
9298
+ await fs9.mkdir(outputPath, { recursive: true });
8777
9299
  for (const [key, value] of Object.entries(payload)) {
8778
- const file = path5.join(outputPath, `${projectId}-${key}-report.json`);
8779
- await fs8.writeFile(
9300
+ const file = path6.join(outputPath, `${projectId}-${key}-report.json`);
9301
+ await fs9.writeFile(
8780
9302
  file,
8781
9303
  `${JSON.stringify(value, null, 2)}
8782
9304
  `,
@@ -8785,8 +9307,8 @@ var downloadReports = async (config, args, auth) => {
8785
9307
  filesWritten.push(file);
8786
9308
  }
8787
9309
  } else {
8788
- await fs8.mkdir(path5.dirname(outputPath), { recursive: true });
8789
- await fs8.writeFile(
9310
+ await fs9.mkdir(path6.dirname(outputPath), { recursive: true });
9311
+ await fs9.writeFile(
8790
9312
  outputPath,
8791
9313
  `${JSON.stringify(data, null, 2)}
8792
9314
  `,
@@ -8795,8 +9317,8 @@ var downloadReports = async (config, args, auth) => {
8795
9317
  filesWritten.push(outputPath);
8796
9318
  }
8797
9319
  } else {
8798
- await fs8.mkdir(path5.dirname(outputPath), { recursive: true });
8799
- await fs8.writeFile(
9320
+ await fs9.mkdir(path6.dirname(outputPath), { recursive: true });
9321
+ await fs9.writeFile(
8800
9322
  outputPath,
8801
9323
  `${JSON.stringify(data, null, 2)}
8802
9324
  `,
@@ -9106,7 +9628,7 @@ var buildToolHandlers = (serverOptions) => {
9106
9628
  if (!rawOutputPath) {
9107
9629
  throw new Error("outputPath is required.");
9108
9630
  }
9109
- const outputPath = path5.resolve(rawOutputPath);
9631
+ const outputPath = path6.resolve(rawOutputPath);
9110
9632
  const publicGraph = booleanValue(args.public) ?? false;
9111
9633
  const auth = publicGraph ? void 0 : await resolveAuth(config, { requireUser: true });
9112
9634
  const layout = normalizeProjectDiagramImageLayout(stringValue(args.layout));
@@ -9127,11 +9649,11 @@ var buildToolHandlers = (serverOptions) => {
9127
9649
  publicGraph,
9128
9650
  syncVersion: stringValue(args.syncVersion)
9129
9651
  });
9130
- await fs8.mkdir(path5.dirname(outputPath), { recursive: true });
9131
- await fs8.writeFile(outputPath, result.bytes);
9652
+ await fs9.mkdir(path6.dirname(outputPath), { recursive: true });
9653
+ await fs9.writeFile(outputPath, result.bytes);
9132
9654
  const filesWritten = [outputPath];
9133
9655
  const rawHeadersOutputPath = stringValue(args.headersOutputPath);
9134
- const headersOutputPath = rawHeadersOutputPath ? path5.resolve(rawHeadersOutputPath) : void 0;
9656
+ const headersOutputPath = rawHeadersOutputPath ? path6.resolve(rawHeadersOutputPath) : void 0;
9135
9657
  if (headersOutputPath) {
9136
9658
  await writeHeaderFile(headersOutputPath, result.headers);
9137
9659
  filesWritten.push(headersOutputPath);
@@ -10003,7 +10525,7 @@ var buildToolHandlers = (serverOptions) => {
10003
10525
  projectId,
10004
10526
  type,
10005
10527
  submitted,
10006
- jobs: submitted.map(extractJobId3).filter(Boolean),
10528
+ jobs: submitted.map(extractJobId4).filter(Boolean),
10007
10529
  finalStatuses
10008
10530
  },
10009
10531
  frontendUrl: reportsFrontendUrl(config, { projectId, type })
@@ -10856,7 +11378,7 @@ var registerMcpCommand = (program2, deps) => {
10856
11378
  note
10857
11379
  });
10858
11380
  if (options.output) {
10859
- await fs8.writeFile(options.output, text, "utf8");
11381
+ await fs9.writeFile(options.output, text, "utf8");
10860
11382
  } else {
10861
11383
  process.stdout.write(text);
10862
11384
  }
@@ -11048,23 +11570,23 @@ Discovery:
11048
11570
  };
11049
11571
 
11050
11572
  // src/credentialsCommand.ts
11051
- var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
11573
+ var asRecord2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
11052
11574
  var arrayFromPayload = (payload, key) => {
11053
- const record = asRecord(payload);
11575
+ const record = asRecord2(payload);
11054
11576
  const value = record[key] ?? record.data;
11055
11577
  return Array.isArray(value) ? value.filter((item) => item && typeof item === "object") : [];
11056
11578
  };
11057
11579
  var credentialFromPayload = (payload) => {
11058
- const record = asRecord(payload);
11059
- return asRecord(record.credential ?? record.data ?? record);
11580
+ const record = asRecord2(payload);
11581
+ return asRecord2(record.credential ?? record.data ?? record);
11060
11582
  };
11061
11583
  var secretFromPayload = (payload) => {
11062
- const record = asRecord(payload);
11584
+ const record = asRecord2(payload);
11063
11585
  const value = record.access_key ?? record.accessKey ?? record.secret;
11064
11586
  return typeof value === "string" ? value : void 0;
11065
11587
  };
11066
11588
  var projectIdFromPayload = (payload, fallback) => {
11067
- const record = asRecord(payload);
11589
+ const record = asRecord2(payload);
11068
11590
  const credential = credentialFromPayload(payload);
11069
11591
  const value = record.project_id ?? record.projectId ?? credential.project_id ?? credential.projectId ?? (Array.isArray(credential.project_ids) ? credential.project_ids[0] : void 0) ?? fallback;
11070
11592
  return typeof value === "string" ? value : void 0;
@@ -11098,7 +11620,7 @@ var writeCredentialOutput = async (input) => {
11098
11620
  return;
11099
11621
  }
11100
11622
  if (input.format === "text" || !input.format) {
11101
- const record = asRecord(input.data);
11623
+ const record = asRecord2(input.data);
11102
11624
  const credentials = arrayFromPayload(input.data, "credentials").length > 0 ? arrayFromPayload(input.data, "credentials") : record.credential ? [credentialFromPayload(input.data)] : arrayFromPayload(input.data, "templates");
11103
11625
  if (credentials.length) {
11104
11626
  process.stdout.write(formatTextTable(formatCredentialTextRows(credentials)));
@@ -11222,9 +11744,9 @@ import { randomUUID as randomUUID4 } from "crypto";
11222
11744
  // src/localHooks.ts
11223
11745
  import { exec } from "child_process";
11224
11746
  import { randomUUID as randomUUID3 } from "crypto";
11225
- import fs9 from "fs/promises";
11747
+ import fs10 from "fs/promises";
11226
11748
  import os4 from "os";
11227
- import path6 from "path";
11749
+ import path7 from "path";
11228
11750
  var normalizeHooks = (config, event) => {
11229
11751
  if (config.hooks?.enabled !== true) {
11230
11752
  return [];
@@ -11235,11 +11757,11 @@ var normalizeHooks = (config, event) => {
11235
11757
  ) : [];
11236
11758
  };
11237
11759
  var writeHookPayload = async (input, hook) => {
11238
- const filePath = path6.join(
11760
+ const filePath = path7.join(
11239
11761
  os4.tmpdir(),
11240
11762
  `cloudeval-hook-${process.pid}-${randomUUID3()}.json`
11241
11763
  );
11242
- await fs9.writeFile(
11764
+ await fs10.writeFile(
11243
11765
  filePath,
11244
11766
  JSON.stringify(
11245
11767
  {
@@ -11315,7 +11837,7 @@ var runLocalHooks = async (input) => {
11315
11837
  throw error;
11316
11838
  }
11317
11839
  } finally {
11318
- await fs9.rm(payloadPath, { force: true }).catch(() => void 0);
11840
+ await fs10.rm(payloadPath, { force: true }).catch(() => void 0);
11319
11841
  }
11320
11842
  }
11321
11843
  return warnings;
@@ -11627,7 +12149,7 @@ var registerAgentsCommand = (program2, deps) => {
11627
12149
 
11628
12150
  // src/validateCommand.ts
11629
12151
  var addCommon5 = (command, deps) => addAuthOptions(command, deps.defaultBaseUrl).requiredOption("--template-file <path>", "Cloud template JSON file").option("--parameters-file <path>", "Optional parameters JSON file").option("--format <format>", "Output format: text, json, ndjson, markdown", "text").option("--output <file>", "Output file");
11630
- var parsePositiveInteger = (value, optionName = "--max-results") => {
12152
+ var parsePositiveInteger2 = (value, optionName = "--max-results") => {
11631
12153
  if (!value) {
11632
12154
  return void 0;
11633
12155
  }
@@ -11661,7 +12183,7 @@ var registerValidateCommand = (program2, deps) => {
11661
12183
  category: options.category,
11662
12184
  pillar: options.pillar,
11663
12185
  minSeverity: options.minSeverity,
11664
- maxResults: parsePositiveInteger(options.maxResults),
12186
+ maxResults: parsePositiveInteger2(options.maxResults),
11665
12187
  projectId: options.project,
11666
12188
  saveReport: options.saveReport
11667
12189
  });
@@ -11670,11 +12192,11 @@ var registerValidateCommand = (program2, deps) => {
11670
12192
  authToken: context.token,
11671
12193
  userId: context.user.id,
11672
12194
  submitted,
11673
- pollIntervalMs: parsePositiveInteger(
12195
+ pollIntervalMs: parsePositiveInteger2(
11674
12196
  options.pollInterval,
11675
12197
  "--poll-interval"
11676
12198
  ),
11677
- waitTimeoutMs: parsePositiveInteger(
12199
+ waitTimeoutMs: parsePositiveInteger2(
11678
12200
  options.waitTimeout,
11679
12201
  "--wait-timeout"
11680
12202
  )
@@ -11734,11 +12256,11 @@ var registerValidateCommand = (program2, deps) => {
11734
12256
  authToken: context.token,
11735
12257
  userId: context.user.id,
11736
12258
  submitted,
11737
- pollIntervalMs: parsePositiveInteger(
12259
+ pollIntervalMs: parsePositiveInteger2(
11738
12260
  options.pollInterval,
11739
12261
  "--poll-interval"
11740
12262
  ),
11741
- waitTimeoutMs: parsePositiveInteger(
12263
+ waitTimeoutMs: parsePositiveInteger2(
11742
12264
  options.waitTimeout,
11743
12265
  "--wait-timeout"
11744
12266
  )
@@ -11859,10 +12381,10 @@ var registerConfigCommand = (program2) => {
11859
12381
  const profile = resolveProfile(options, command);
11860
12382
  const current = await loadCliConfig(profile);
11861
12383
  const next = writeCliConfigValue(current, key, value);
11862
- const path10 = await saveCliConfig(next, profile);
12384
+ const path11 = await saveCliConfig(next, profile);
11863
12385
  await writeFormattedOutput({
11864
12386
  command: "config set",
11865
- data: { profile, path: path10, config: next },
12387
+ data: { profile, path: path11, config: next },
11866
12388
  format: options.format,
11867
12389
  output: options.output
11868
12390
  });
@@ -11873,10 +12395,10 @@ var registerConfigCommand = (program2) => {
11873
12395
  const profile = resolveProfile(options, command);
11874
12396
  const current = await loadCliConfig(profile);
11875
12397
  const next = unsetCliConfigValue(current, key);
11876
- const path10 = await saveCliConfig(next, profile);
12398
+ const path11 = await saveCliConfig(next, profile);
11877
12399
  await writeFormattedOutput({
11878
12400
  command: "config unset",
11879
- data: { profile, path: path10, config: next },
12401
+ data: { profile, path: path11, config: next },
11880
12402
  format: options.format,
11881
12403
  output: options.output
11882
12404
  });
@@ -12173,8 +12695,8 @@ var writeModelsListOutput = async (input) => {
12173
12695
  defaultModel: input.defaultModel
12174
12696
  });
12175
12697
  if (input.options.output) {
12176
- const fs13 = await import("fs/promises");
12177
- await fs13.writeFile(input.options.output, text, "utf8");
12698
+ const fs14 = await import("fs/promises");
12699
+ await fs14.writeFile(input.options.output, text, "utf8");
12178
12700
  return;
12179
12701
  }
12180
12702
  process.stdout.write(text);
@@ -12240,10 +12762,10 @@ var registerModelsCommand = (program2, deps) => {
12240
12762
  const profile = options.profile || getActiveConfigProfile(command);
12241
12763
  const config = await loadCliConfig(profile);
12242
12764
  const next = { ...config, model };
12243
- const path10 = await saveCliConfig(next, profile);
12765
+ const path11 = await saveCliConfig(next, profile);
12244
12766
  await writeFormattedOutput({
12245
12767
  command: "models default set",
12246
- data: { profile, path: path10, model },
12768
+ data: { profile, path: path11, model },
12247
12769
  format: options.format,
12248
12770
  output: options.output
12249
12771
  });
@@ -12286,8 +12808,8 @@ var writeSessionTableOutput = async (command, data, options) => {
12286
12808
  if (format === "text") {
12287
12809
  const text = renderSessionsTable(data);
12288
12810
  if (options.output) {
12289
- const fs13 = await import("fs/promises");
12290
- await fs13.writeFile(options.output, text, "utf8");
12811
+ const fs14 = await import("fs/promises");
12812
+ await fs14.writeFile(options.output, text, "utf8");
12291
12813
  return;
12292
12814
  }
12293
12815
  process.stdout.write(text);
@@ -12459,12 +12981,12 @@ var registerSetupCommand = (program2, defaultBaseUrl = CLOUD_BASE_URL) => {
12459
12981
  rl.close();
12460
12982
  }
12461
12983
  }
12462
- const path10 = await saveCliConfig(next, profile);
12984
+ const path11 = await saveCliConfig(next, profile);
12463
12985
  await writeFormattedOutput({
12464
12986
  command: "setup",
12465
12987
  data: {
12466
12988
  profile,
12467
- path: path10,
12989
+ path: path11,
12468
12990
  config: next,
12469
12991
  nextSteps: [
12470
12992
  "Run `cloudeval auth status` to inspect authentication.",
@@ -12479,9 +13001,9 @@ var registerSetupCommand = (program2, defaultBaseUrl = CLOUD_BASE_URL) => {
12479
13001
  };
12480
13002
 
12481
13003
  // src/updateCommand.ts
12482
- import { spawn as spawn2 } from "child_process";
12483
- import fs10 from "fs/promises";
12484
- import path7 from "path";
13004
+ import { spawn as spawn3 } from "child_process";
13005
+ import fs11 from "fs/promises";
13006
+ import path8 from "path";
12485
13007
  import { createInterface as createInterface2 } from "readline/promises";
12486
13008
  var DEFAULT_LATEST_RELEASE_URL = "https://api.github.com/repos/ganakailabs/cloudeval-cli/releases/latest";
12487
13009
  var DEFAULT_INSTALLER_URL = "https://cli.cloudeval.ai/install.sh";
@@ -12598,7 +13120,7 @@ var runInstaller = async ({
12598
13120
  installerUrl,
12599
13121
  targetTag,
12600
13122
  fetchImpl = fetch,
12601
- spawnImpl = spawn2,
13123
+ spawnImpl = spawn3,
12602
13124
  output = process.stderr,
12603
13125
  platform = process.platform,
12604
13126
  env = process.env,
@@ -12620,12 +13142,12 @@ var runInstaller = async ({
12620
13142
  const installerScript = await response.text();
12621
13143
  if (usePowerShellInstaller) {
12622
13144
  const configDir = getCloudevalConfigDir();
12623
- await fs10.mkdir(configDir, { recursive: true, mode: 448 });
12624
- const scriptPath = path7.join(
12625
- await fs10.mkdtemp(path7.join(configDir, "installer-")),
13145
+ await fs11.mkdir(configDir, { recursive: true, mode: 448 });
13146
+ const scriptPath = path8.join(
13147
+ await fs11.mkdtemp(path8.join(configDir, "installer-")),
12626
13148
  "install.ps1"
12627
13149
  );
12628
- await fs10.writeFile(scriptPath, installerScript, "utf8");
13150
+ await fs11.writeFile(scriptPath, installerScript, "utf8");
12629
13151
  const child2 = spawnImpl(
12630
13152
  "pwsh",
12631
13153
  [
@@ -12653,7 +13175,7 @@ var runInstaller = async ({
12653
13175
  );
12654
13176
  });
12655
13177
  child2.once("close", (code) => {
12656
- void fs10.rm(path7.dirname(scriptPath), { recursive: true, force: true });
13178
+ void fs11.rm(path8.dirname(scriptPath), { recursive: true, force: true });
12657
13179
  if (code === 0) {
12658
13180
  resolve();
12659
13181
  return;
@@ -12693,10 +13215,10 @@ var runInstaller = async ({
12693
13215
  child.stdin.end(installerScript);
12694
13216
  });
12695
13217
  };
12696
- var getUpdateCachePath = () => path7.join(getCloudevalConfigDir(), UPDATE_CACHE_FILE);
13218
+ var getUpdateCachePath = () => path8.join(getCloudevalConfigDir(), UPDATE_CACHE_FILE);
12697
13219
  var readCache = async (cachePath) => {
12698
13220
  try {
12699
- const parsed = JSON.parse(await fs10.readFile(cachePath, "utf8"));
13221
+ const parsed = JSON.parse(await fs11.readFile(cachePath, "utf8"));
12700
13222
  if (parsed && typeof parsed === "object" && typeof parsed.checkedAt === "string" && typeof parsed.latestVersion === "string" && typeof parsed.latestTag === "string") {
12701
13223
  return parsed;
12702
13224
  }
@@ -12708,8 +13230,8 @@ var readCache = async (cachePath) => {
12708
13230
  return void 0;
12709
13231
  };
12710
13232
  var writeCache = async (cachePath, status) => {
12711
- await fs10.mkdir(path7.dirname(cachePath), { recursive: true, mode: 448 });
12712
- await fs10.writeFile(
13233
+ await fs11.mkdir(path8.dirname(cachePath), { recursive: true, mode: 448 });
13234
+ await fs11.writeFile(
12713
13235
  cachePath,
12714
13236
  `${JSON.stringify(
12715
13237
  {
@@ -12921,7 +13443,7 @@ var registerUpdateCommand = (program2, registerOptions = {}) => {
12921
13443
  if (options.format === "text" || !options.format) {
12922
13444
  const text = formatUpdateStatusText(result);
12923
13445
  if (options.output) {
12924
- await fs10.writeFile(options.output, text, "utf8");
13446
+ await fs11.writeFile(options.output, text, "utf8");
12925
13447
  return;
12926
13448
  }
12927
13449
  process.stdout.write(text);
@@ -12937,13 +13459,13 @@ var registerUpdateCommand = (program2, registerOptions = {}) => {
12937
13459
  };
12938
13460
 
12939
13461
  // src/uninstallCommand.ts
12940
- import fs11 from "fs/promises";
13462
+ import fs12 from "fs/promises";
12941
13463
  import os5 from "os";
12942
- import path8 from "path";
13464
+ import path9 from "path";
12943
13465
  import { createInterface as createInterface3 } from "readline/promises";
12944
13466
  var pathExists = async (candidate) => {
12945
13467
  try {
12946
- await fs11.lstat(candidate);
13468
+ await fs12.lstat(candidate);
12947
13469
  return true;
12948
13470
  } catch (error) {
12949
13471
  if (error?.code === "ENOENT") {
@@ -12952,12 +13474,12 @@ var pathExists = async (candidate) => {
12952
13474
  throw error;
12953
13475
  }
12954
13476
  };
12955
- var installerBinDir = (home) => path8.join(home, ".local", "bin");
13477
+ var installerBinDir = (home) => path9.join(home, ".local", "bin");
12956
13478
  var completionPaths = (home) => [
12957
- path8.join(home, ".local", "share", "bash-completion", "completions", "cloudeval"),
12958
- path8.join(home, ".zsh", "completions", "_cloudeval"),
12959
- path8.join(home, ".config", "fish", "completions", "cloudeval.fish"),
12960
- path8.join(home, ".config", "powershell", "cloudeval-completion.ps1")
13479
+ path9.join(home, ".local", "share", "bash-completion", "completions", "cloudeval"),
13480
+ path9.join(home, ".zsh", "completions", "_cloudeval"),
13481
+ path9.join(home, ".config", "fish", "completions", "cloudeval.fish"),
13482
+ path9.join(home, ".config", "powershell", "cloudeval-completion.ps1")
12961
13483
  ];
12962
13484
  var installerArtifactTargets = (home, platform) => {
12963
13485
  const binDir = installerBinDir(home);
@@ -12965,37 +13487,37 @@ var installerArtifactTargets = (home, platform) => {
12965
13487
  const targets = [
12966
13488
  {
12967
13489
  label: "cloudeval binary",
12968
- path: path8.join(binDir, executableName),
13490
+ path: path9.join(binDir, executableName),
12969
13491
  kind: "file",
12970
13492
  status: "missing"
12971
13493
  },
12972
13494
  {
12973
13495
  label: "cloudeval binary",
12974
- path: path8.join(binDir, "cloudeval"),
13496
+ path: path9.join(binDir, "cloudeval"),
12975
13497
  kind: "file",
12976
13498
  status: "missing"
12977
13499
  },
12978
13500
  {
12979
13501
  label: "eva alias",
12980
- path: path8.join(binDir, "eva"),
13502
+ path: path9.join(binDir, "eva"),
12981
13503
  kind: "file",
12982
13504
  status: "missing"
12983
13505
  },
12984
13506
  {
12985
13507
  label: "cloud alias",
12986
- path: path8.join(binDir, "cloud"),
13508
+ path: path9.join(binDir, "cloud"),
12987
13509
  kind: "file",
12988
13510
  status: "missing"
12989
13511
  },
12990
13512
  {
12991
13513
  label: "Ink runtime asset",
12992
- path: path8.join(binDir, "yoga.wasm"),
13514
+ path: path9.join(binDir, "yoga.wasm"),
12993
13515
  kind: "file",
12994
13516
  status: "missing"
12995
13517
  },
12996
13518
  {
12997
13519
  label: "license notices",
12998
- path: path8.join(home, ".local", "share", "cloudeval", "licenses"),
13520
+ path: path9.join(home, ".local", "share", "cloudeval", "licenses"),
12999
13521
  kind: "directory",
13000
13522
  status: "missing"
13001
13523
  },
@@ -13011,11 +13533,11 @@ var installerArtifactTargets = (home, platform) => {
13011
13533
  );
13012
13534
  };
13013
13535
  var shellProfilePaths = (home) => [
13014
- path8.join(home, ".bashrc"),
13015
- path8.join(home, ".bash_profile"),
13016
- path8.join(home, ".zshrc"),
13017
- path8.join(home, ".profile"),
13018
- path8.join(home, ".config", "fish", "config.fish")
13536
+ path9.join(home, ".bashrc"),
13537
+ path9.join(home, ".bash_profile"),
13538
+ path9.join(home, ".zshrc"),
13539
+ path9.join(home, ".profile"),
13540
+ path9.join(home, ".config", "fish", "config.fish")
13019
13541
  ];
13020
13542
  var removeInstallerPathSnippet = (content, binDir) => {
13021
13543
  const exportLine = `export PATH="${binDir}:$PATH"`;
@@ -13042,7 +13564,7 @@ var removeTarget = async (target, dryRun) => {
13042
13564
  if (dryRun) {
13043
13565
  return { ...target, status: "would_remove" };
13044
13566
  }
13045
- await fs11.rm(target.path, { recursive: target.kind === "directory", force: true });
13567
+ await fs12.rm(target.path, { recursive: target.kind === "directory", force: true });
13046
13568
  return { ...target, status: "removed" };
13047
13569
  };
13048
13570
  var updateShellProfile = async (profilePath, home, dryRun) => {
@@ -13054,7 +13576,7 @@ var updateShellProfile = async (profilePath, home, dryRun) => {
13054
13576
  status: "missing"
13055
13577
  };
13056
13578
  }
13057
- const content = await fs11.readFile(profilePath, "utf8");
13579
+ const content = await fs12.readFile(profilePath, "utf8");
13058
13580
  const updated = removeInstallerPathSnippet(content, installerBinDir(home));
13059
13581
  if (updated === void 0) {
13060
13582
  return {
@@ -13072,7 +13594,7 @@ var updateShellProfile = async (profilePath, home, dryRun) => {
13072
13594
  status: "would_update"
13073
13595
  };
13074
13596
  }
13075
- await fs11.writeFile(profilePath, updated, "utf8");
13597
+ await fs12.writeFile(profilePath, updated, "utf8");
13076
13598
  return {
13077
13599
  label: "shell profile PATH entry",
13078
13600
  path: profilePath,
@@ -13125,7 +13647,7 @@ var handleUninstallCommand = async (options, deps = {}) => {
13125
13647
  }
13126
13648
  const configTarget = {
13127
13649
  label: "config",
13128
- path: path8.join(home, ".config", "cloudeval"),
13650
+ path: path9.join(home, ".config", "cloudeval"),
13129
13651
  kind: "directory",
13130
13652
  status: "kept"
13131
13653
  };
@@ -13187,7 +13709,7 @@ var registerUninstallCommand = (program2) => {
13187
13709
  if (options.format === "text" || !options.format) {
13188
13710
  const text = formatUninstallResultText(result);
13189
13711
  if (options.output) {
13190
- await fs11.writeFile(options.output, text, "utf8");
13712
+ await fs12.writeFile(options.output, text, "utf8");
13191
13713
  return;
13192
13714
  }
13193
13715
  process.stdout.write(text);
@@ -13308,6 +13830,7 @@ var resolveLoginOnboardingMode = (options) => {
13308
13830
  import { jsx } from "react/jsx-runtime";
13309
13831
  var DEFAULT_BASE_URL = getDefaultBaseUrl();
13310
13832
  var ASK_STREAM_IDLE_TIMEOUT_MS2 = 9e4;
13833
+ var AGENT_STREAM_IDLE_TIMEOUT_MS = 18e4;
13311
13834
  var LEGACY_API_KEY_MESSAGE = "API key auth was renamed in beta. Use --access-key or CLOUDEVAL_ACCESS_KEY.";
13312
13835
  var STREAM_OUTPUT_NODES3 = /* @__PURE__ */ new Set([
13313
13836
  "generate_response",
@@ -13333,21 +13856,21 @@ var completionScriptPath = (shell) => {
13333
13856
  const home = os6.homedir();
13334
13857
  switch (shell) {
13335
13858
  case "bash":
13336
- return path9.join(home, ".local", "share", "bash-completion", "completions", "cloudeval");
13859
+ return path10.join(home, ".local", "share", "bash-completion", "completions", "cloudeval");
13337
13860
  case "zsh":
13338
- return path9.join(home, ".zsh", "completions", "_cloudeval");
13861
+ return path10.join(home, ".zsh", "completions", "_cloudeval");
13339
13862
  case "fish":
13340
- return path9.join(home, ".config", "fish", "completions", "cloudeval.fish");
13863
+ return path10.join(home, ".config", "fish", "completions", "cloudeval.fish");
13341
13864
  case "powershell":
13342
- return path9.join(home, ".config", "powershell", "cloudeval-completion.ps1");
13865
+ return path10.join(home, ".config", "powershell", "cloudeval-completion.ps1");
13343
13866
  }
13344
13867
  };
13345
13868
  var ZSH_FPATH_MARKER = "CloudEval CLI completions";
13346
13869
  var ensureZshCompletionFpath = async () => {
13347
- const zshrc = path9.join(os6.homedir(), ".zshrc");
13870
+ const zshrc = path10.join(os6.homedir(), ".zshrc");
13348
13871
  let existing = "";
13349
13872
  try {
13350
- existing = await fs12.readFile(zshrc, "utf8");
13873
+ existing = await fs13.readFile(zshrc, "utf8");
13351
13874
  } catch {
13352
13875
  existing = "";
13353
13876
  }
@@ -13358,12 +13881,12 @@ var ensureZshCompletionFpath = async () => {
13358
13881
  # ${ZSH_FPATH_MARKER}
13359
13882
  fpath=("$HOME/.zsh/completions" $fpath)
13360
13883
  `;
13361
- await fs12.appendFile(zshrc, snippet, "utf8");
13884
+ await fs13.appendFile(zshrc, snippet, "utf8");
13362
13885
  };
13363
13886
  var installCompletionScript = async (shell, binaryName) => {
13364
13887
  const scriptPath = completionScriptPath(shell);
13365
- await fs12.mkdir(path9.dirname(scriptPath), { recursive: true });
13366
- await fs12.writeFile(scriptPath, buildCompletionScript(shell, binaryName), "utf8");
13888
+ await fs13.mkdir(path10.dirname(scriptPath), { recursive: true });
13889
+ await fs13.writeFile(scriptPath, buildCompletionScript(shell, binaryName), "utf8");
13367
13890
  if (shell === "zsh") {
13368
13891
  await ensureZshCompletionFpath();
13369
13892
  }
@@ -13371,7 +13894,7 @@ var installCompletionScript = async (shell, binaryName) => {
13371
13894
  };
13372
13895
  var uninstallCompletionScript = async (shell) => {
13373
13896
  const scriptPath = completionScriptPath(shell);
13374
- await fs12.rm(scriptPath, { force: true });
13897
+ await fs13.rm(scriptPath, { force: true });
13375
13898
  return scriptPath;
13376
13899
  };
13377
13900
  var runInteractiveLoginOnboarding = async (baseUrl, token) => {
@@ -13940,6 +14463,12 @@ registerReportsCommand(program, {
13940
14463
  resolveBaseUrl,
13941
14464
  readStdinValue
13942
14465
  });
14466
+ registerReviewCommand(program, {
14467
+ defaultBaseUrl: DEFAULT_BASE_URL,
14468
+ resolveBaseUrl,
14469
+ readStdinValue,
14470
+ isHeadlessEnvironment
14471
+ });
13943
14472
  registerRecipesCommand(program, {
13944
14473
  defaultBaseUrl: DEFAULT_BASE_URL,
13945
14474
  resolveBaseUrl,
@@ -14096,7 +14625,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option(
14096
14625
  const { assertSecureBaseUrl } = await import("./dist-CFLR5FML.js");
14097
14626
  const [{ render }, { App }] = await Promise.all([
14098
14627
  import("ink"),
14099
- import("./App-YO6YXOII.js")
14628
+ import("./App-H46FRLWK.js")
14100
14629
  ]);
14101
14630
  const baseUrl = await resolveBaseUrl(options, command);
14102
14631
  assertSecureBaseUrl(baseUrl);
@@ -14154,7 +14683,7 @@ program.command("chat").description("Start an interactive chat session").option(
14154
14683
  const { assertSecureBaseUrl } = await import("./dist-CFLR5FML.js");
14155
14684
  const [{ render }, { App }] = await Promise.all([
14156
14685
  import("ink"),
14157
- import("./App-YO6YXOII.js")
14686
+ import("./App-H46FRLWK.js")
14158
14687
  ]);
14159
14688
  const baseUrl = await resolveBaseUrl(options, command);
14160
14689
  assertSecureBaseUrl(baseUrl);
@@ -14267,7 +14796,7 @@ program.command("ask").alias("agent").description("Ask a single question or run
14267
14796
  });
14268
14797
  }
14269
14798
  try {
14270
- const fs13 = await import("fs");
14799
+ const fs14 = await import("fs");
14271
14800
  const fsPromises = await import("fs/promises");
14272
14801
  const { randomUUID: randomUUID5 } = await import("crypto");
14273
14802
  const core = await import("./dist-CFLR5FML.js");
@@ -14275,8 +14804,6 @@ program.command("ask").alias("agent").description("Ask a single question or run
14275
14804
  streamChat,
14276
14805
  reduceChunk,
14277
14806
  getAuthToken,
14278
- getProjects,
14279
- ensurePlaygroundProject,
14280
14807
  checkUserStatus,
14281
14808
  extractEmailFromToken,
14282
14809
  initialChatState,
@@ -14375,68 +14902,39 @@ program.command("ask").alias("agent").description("Ask a single question or run
14375
14902
  step: "project",
14376
14903
  message: selectedProjectId ? `Using project ${selectedProjectId}` : "Resolving project"
14377
14904
  });
14378
- let project;
14379
14905
  let authenticatedUserId;
14380
- if (selectedProjectId) {
14381
- verboseLog("Using provided project ID:", selectedProjectId);
14382
- try {
14383
- const userStatus = await checkUserStatus(baseUrl, token);
14384
- getActiveCliTelemetry()?.setUserProperties(userStatus.user || {});
14385
- authenticatedUserId = userStatus.user?.id;
14386
- } catch {
14387
- }
14388
- project = {
14389
- id: selectedProjectId,
14390
- name: "Selected Project",
14391
- user_id: authenticatedUserId,
14392
- cloud_provider: "azure"
14393
- };
14394
- } else {
14395
- verboseLog("No project ID provided, attempting to fetch user projects");
14396
- try {
14397
- verboseLog("Checking user status", { baseUrl });
14398
- const userStatus = await checkUserStatus(baseUrl, token);
14399
- getActiveCliTelemetry()?.setUserProperties(userStatus.user || {});
14400
- authenticatedUserId = userStatus.user?.id;
14401
- verboseLog("User status:", {
14402
- hasUser: !!userStatus.user,
14403
- userId: userStatus.user?.id,
14404
- onboardingCompleted: userStatus.onboardingCompleted
14405
- });
14406
- if (authenticatedUserId) {
14407
- verboseLog("Fetching projects for user", { userId: authenticatedUserId });
14408
- const projects = await getProjects(baseUrl, token, authenticatedUserId);
14409
- verboseLog("Projects fetched:", { count: projects.length, names: projects.map((p) => p.name) });
14410
- const playgroundProject = projects.find((p) => p.name === "Playground");
14411
- if (playgroundProject) {
14412
- project = playgroundProject;
14413
- } else if (userStatus.user?.email) {
14414
- verboseLog("Playground project missing; running shared onboarding repair");
14415
- project = await ensurePlaygroundProject(baseUrl, token, {
14416
- id: authenticatedUserId,
14417
- email: userStatus.user.email,
14418
- full_name: userStatus.user.full_name,
14419
- name: userStatus.user.name
14420
- });
14421
- } else {
14422
- project = projects[0] || void 0;
14423
- }
14424
- verboseLog("Selected project:", project ? { id: project.id, name: project.name } : "none");
14425
- }
14426
- } catch (error) {
14427
- verboseLog("Failed to fetch projects, using default:", {
14428
- message: error.message,
14429
- stack: error.stack
14430
- });
14431
- }
14432
- if (!project) {
14433
- process.stderr.write(
14434
- "No project is available for this account. Run `cloudeval chat` to complete onboarding, then retry."
14435
- );
14436
- process.stderr.write("\n");
14437
- await exitCli(1, new Error("no_project_available"));
14438
- }
14906
+ let authenticatedUser;
14907
+ try {
14908
+ const userStatus = await checkUserStatus(baseUrl, token);
14909
+ getActiveCliTelemetry()?.setUserProperties(userStatus.user || {});
14910
+ authenticatedUserId = userStatus.user?.id;
14911
+ authenticatedUser = userStatus.user;
14912
+ verboseLog("User status:", {
14913
+ hasUser: !!userStatus.user,
14914
+ userId: userStatus.user?.id,
14915
+ onboardingCompleted: userStatus.onboardingCompleted
14916
+ });
14917
+ } catch (error) {
14918
+ verboseLog("Failed to check user status before project resolve:", {
14919
+ message: error.message
14920
+ });
14921
+ }
14922
+ const { resolveAskProject } = await import("./resolveAskProject-NK435I56.js");
14923
+ let project;
14924
+ try {
14925
+ project = await resolveAskProject({
14926
+ baseUrl,
14927
+ token,
14928
+ selectedProjectId,
14929
+ authenticatedUserId,
14930
+ authenticatedUser
14931
+ });
14932
+ } catch (error) {
14933
+ progressWriter.clear();
14934
+ console.error(error?.message ?? "Failed to resolve project");
14935
+ await exitCli(1, error);
14439
14936
  }
14937
+ verboseLog("Selected project:", { id: project.id, name: project.name });
14440
14938
  let userName = "You";
14441
14939
  try {
14442
14940
  const email = extractEmailFromToken(token);
@@ -14463,11 +14961,11 @@ program.command("ask").alias("agent").description("Ask a single question or run
14463
14961
  console.error(`[${commandName}] Thread ID: ${threadId}`);
14464
14962
  }
14465
14963
  if (streamTextOutput && options.output) {
14466
- fileOutputStream = fs13.createWriteStream(options.output, { encoding: "utf-8" });
14964
+ fileOutputStream = fs14.createWriteStream(options.output, { encoding: "utf-8" });
14467
14965
  outputStream = fileOutputStream;
14468
14966
  }
14469
14967
  if (ndjsonOutput && options.output) {
14470
- ndjsonOutputStream = fs13.createWriteStream(options.output, { encoding: "utf-8" });
14968
+ ndjsonOutputStream = fs14.createWriteStream(options.output, { encoding: "utf-8" });
14471
14969
  }
14472
14970
  const writeAskDataEvent = (event) => {
14473
14971
  const line = `${JSON.stringify(event)}
@@ -14569,7 +15067,7 @@ program.command("ask").alias("agent").description("Ask a single question or run
14569
15067
  debug: options.debug,
14570
15068
  completeAfterResponse: true,
14571
15069
  responseCompletionGraceMs: 5e3,
14572
- streamIdleTimeoutMs: ASK_STREAM_IDLE_TIMEOUT_MS2,
15070
+ streamIdleTimeoutMs: selectedMode === "agent" ? AGENT_STREAM_IDLE_TIMEOUT_MS : ASK_STREAM_IDLE_TIMEOUT_MS2,
14573
15071
  hitlResume
14574
15072
  })) {
14575
15073
  totalChunkCount++;
@@ -14748,7 +15246,24 @@ Error: ${errorMsg}
14748
15246
  throw error;
14749
15247
  }
14750
15248
  const finalMessage = [...chatState.messages].reverse().find((m) => m.role === "assistant");
14751
- const finalResponse = collapseRepeatedAssistantText3(finalMessage?.content || responseText || "");
15249
+ let finalResponse = collapseRepeatedAssistantText3(
15250
+ finalMessage?.content || responseText || ""
15251
+ );
15252
+ if (!finalResponse.trim() && chatState.threadId) {
15253
+ const { fetchLastAssistantContent } = await import("./fetchLastAssistantContent-RH6RMSQO.js");
15254
+ const persisted = await fetchLastAssistantContent({
15255
+ baseUrl,
15256
+ authToken: token,
15257
+ threadId: chatState.threadId,
15258
+ normalizeApiBase: normalizeApiBase2
15259
+ });
15260
+ if (persisted) {
15261
+ finalResponse = collapseRepeatedAssistantText3(persisted);
15262
+ verboseLog("Recovered final response from thread history", {
15263
+ length: finalResponse.length
15264
+ });
15265
+ }
15266
+ }
14752
15267
  if (!finalResponse.trim()) {
14753
15268
  const noResponseMessage = `No final response returned by CloudEval (last stream status: ${chatState.status ?? "unknown"}). Retry with --verbose or --format ndjson to inspect stream progress.`;
14754
15269
  progressWriter.clear();
@@ -14922,7 +15437,7 @@ Error: ${errorMsg}
14922
15437
  program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
14923
15438
  const { render } = await import("ink");
14924
15439
  const BannerPreview = React.lazy(async () => ({
14925
- default: (await import("./Banner-CSZZHF6L.js")).Banner
15440
+ default: (await import("./Banner-7X2VHUVH.js")).Banner
14926
15441
  }));
14927
15442
  render(
14928
15443
  /* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(BannerPreview, { disable: false }) })