@ganakailabs/cloudeval-cli 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3472 @@
1
+ // ../shared/dist/index.js
2
+ var isObject = (value) => typeof value === "object" && value !== null;
3
+ var stringOr = (value, fallback) => typeof value === "string" && value.trim() ? value : fallback;
4
+ var normalizeProvider = (value) => {
5
+ if (value === "azure" || value === "aws" || value === "gcp") {
6
+ return value;
7
+ }
8
+ return "unknown";
9
+ };
10
+ var normalizeSections = (value) => {
11
+ if (!Array.isArray(value)) {
12
+ return [];
13
+ }
14
+ return value.map((section, index) => {
15
+ if (!isObject(section)) {
16
+ return void 0;
17
+ }
18
+ return {
19
+ id: stringOr(section.id, `section-${index + 1}`),
20
+ title: stringOr(section.title, `Section ${index + 1}`),
21
+ markdown: stringOr(section.markdown, "")
22
+ };
23
+ }).filter((section) => Boolean(section));
24
+ };
25
+ var normalizeReportEnvelope = (input) => {
26
+ const rawInput = isObject(input) && isObject(input.report) ? input.report : input;
27
+ if (!isObject(rawInput)) {
28
+ throw new Error("Report response must be an object.");
29
+ }
30
+ const source = isObject(rawInput.source) ? rawInput.source : {};
31
+ const formatted = isObject(rawInput.formatted) ? rawInput.formatted : void 0;
32
+ const period = isObject(rawInput.period) ? {
33
+ start: stringOr(rawInput.period.start, ""),
34
+ end: stringOr(rawInput.period.end, "")
35
+ } : void 0;
36
+ return {
37
+ id: stringOr(rawInput.id, "unknown-report"),
38
+ kind: rawInput.kind === "waf" ? "waf" : "cost",
39
+ projectId: stringOr(rawInput.projectId ?? rawInput.project_id, "unknown-project"),
40
+ generatedAt: stringOr(
41
+ rawInput.generatedAt ?? rawInput.generated_at,
42
+ (/* @__PURE__ */ new Date(0)).toISOString()
43
+ ),
44
+ period,
45
+ source: {
46
+ provider: normalizeProvider(source.provider ?? rawInput.provider),
47
+ backendVersion: typeof source.backendVersion === "string" ? source.backendVersion : typeof source.backend_version === "string" ? source.backend_version : void 0,
48
+ evidenceRefs: Array.isArray(source.evidenceRefs) ? source.evidenceRefs.filter((ref) => typeof ref === "string") : Array.isArray(source.evidence_refs) ? source.evidence_refs.filter((ref) => typeof ref === "string") : void 0
49
+ },
50
+ raw: rawInput.raw ?? {},
51
+ parsed: rawInput.parsed ?? {},
52
+ formatted: formatted ? {
53
+ title: stringOr(formatted.title, "Report"),
54
+ summary: stringOr(formatted.summary, ""),
55
+ sections: normalizeSections(formatted.sections)
56
+ } : void 0
57
+ };
58
+ };
59
+ var normalizeReportList = (input) => {
60
+ const list = Array.isArray(input) ? input : isObject(input) && Array.isArray(input.reports) ? input.reports : isObject(input) && Array.isArray(input.data) ? input.data : isObject(input) && Array.isArray(input.items) ? input.items : [];
61
+ return list.map((item) => normalizeReportEnvelope(item));
62
+ };
63
+ var SECRET_REDACTION = "[redacted]";
64
+ var SENSITIVE_KEY_PATTERN = /token|authorization|cookie|secret|password|api[_-]?key|access[_-]?key|client[_-]?secret|refresh|device[_-]?code|user[_-]?code/i;
65
+ var SENSITIVE_QUERY_PARAM_PATTERN = /token|authorization|cookie|secret|password|api[_-]?key|access[_-]?key|client[_-]?secret|refresh|device[_-]?code|user[_-]?code|code/i;
66
+ var CLOUDEVAL_ACCESS_KEY_VALUE_PATTERN = /\bcev_[a-z0-9]+_ak_[A-Za-z0-9]+_[A-Za-z0-9._~+-]+(?:_[A-Za-z0-9._~+-]+)*\b/gi;
67
+ var AUTHORIZATION_BEARER_PATTERN = /\b(authorization\s*:\s*bearer\s+)([^\s'",}]+)/gi;
68
+ var isSensitiveSecretKey = (key) => SENSITIVE_KEY_PATTERN.test(key);
69
+ var redactSensitiveText = (value) => {
70
+ let text = value;
71
+ try {
72
+ const parsed = JSON.parse(text);
73
+ if (parsed && typeof parsed === "object") {
74
+ return JSON.stringify(redactSensitiveSecrets(parsed));
75
+ }
76
+ } catch {
77
+ }
78
+ try {
79
+ const url = new URL(text);
80
+ let changed = false;
81
+ for (const key of Array.from(url.searchParams.keys())) {
82
+ if (SENSITIVE_QUERY_PARAM_PATTERN.test(key)) {
83
+ url.searchParams.set(key, SECRET_REDACTION);
84
+ changed = true;
85
+ }
86
+ }
87
+ if (changed) {
88
+ text = url.toString();
89
+ }
90
+ } catch {
91
+ }
92
+ return text.replace(AUTHORIZATION_BEARER_PATTERN, (_match, prefix) => `${prefix}${SECRET_REDACTION}`).replace(CLOUDEVAL_ACCESS_KEY_VALUE_PATTERN, SECRET_REDACTION);
93
+ };
94
+ var redactSensitiveSecrets = (value) => {
95
+ if (Array.isArray(value)) {
96
+ return value.map((item) => redactSensitiveSecrets(item));
97
+ }
98
+ if (typeof value === "string") {
99
+ return redactSensitiveText(value);
100
+ }
101
+ if (value && typeof value === "object") {
102
+ const redacted = {};
103
+ for (const [key, item] of Object.entries(value)) {
104
+ redacted[key] = item === null || item === void 0 ? item : isSensitiveSecretKey(key) ? SECRET_REDACTION : redactSensitiveSecrets(item);
105
+ }
106
+ return redacted;
107
+ }
108
+ return value;
109
+ };
110
+ var baseSettings = {
111
+ mode: "agent",
112
+ response_length: "Detailed",
113
+ technicality: "Expert",
114
+ reasoning_effort: "medium",
115
+ enable_judge: true,
116
+ enable_hitl: true
117
+ };
118
+ var BUNDLED_AGENT_PROFILES = [
119
+ {
120
+ id: "architecture",
121
+ display_name: "Architecture",
122
+ description: "Reviews topology, dependencies, blast radius, reliability, security, cost, operational excellence, and performance signals.",
123
+ personality: "Systems-minded, concise, and dependency-aware.",
124
+ accent_key: "blue",
125
+ icon_key: "network",
126
+ default_mode: "agent",
127
+ starter_prompt: "Review this project architecture for the highest-impact risks and next actions.",
128
+ starter_prompts: {
129
+ template: "Review this template architecture for topology, dependency, and platform-design risks.",
130
+ sync: "Review this live environment architecture for topology, dependency, and blast-radius risks."
131
+ },
132
+ starter_prompt_variants: [
133
+ {
134
+ id: "architecture-template-agent",
135
+ project_source: "template",
136
+ mode: "agent",
137
+ text: "Review this template architecture for topology, dependency, and platform-design risks.",
138
+ weight: 1
139
+ },
140
+ {
141
+ id: "architecture-sync-agent",
142
+ project_source: "sync",
143
+ mode: "agent",
144
+ text: "Review this live environment architecture for topology, dependency, and blast-radius risks.",
145
+ weight: 1
146
+ }
147
+ ],
148
+ system_instructions: "Use the Architecture lens. Prioritize topology, dependencies, blast radius, reliability, security, cost, operations, and performance evidence. Return risk-ranked findings with concrete validation steps.",
149
+ default_settings: { ...baseSettings },
150
+ required_capabilities: ["projects:read", "reports:read", "ask:run"],
151
+ allowed_toolsets: ["projects", "reports", "graph", "rules"],
152
+ output_contract: {
153
+ sections: ["interpretation", "top_signals", "actions", "checkpoint"]
154
+ }
155
+ },
156
+ {
157
+ id: "cost",
158
+ display_name: "Cost",
159
+ description: "Reviews project cost drivers, waste signals, and practical optimization opportunities.",
160
+ personality: "Commercially pragmatic, specific, and action-oriented.",
161
+ accent_key: "emerald",
162
+ icon_key: "wallet",
163
+ default_mode: "agent",
164
+ starter_prompt: "Review this project for cost drivers, waste signals, and practical savings actions.",
165
+ starter_prompts: {
166
+ template: "Review this template for cost drivers, expensive defaults, and safer sizing choices.",
167
+ sync: "Review this live environment for waste signals, savings opportunities, and ownership gaps."
168
+ },
169
+ starter_prompt_variants: [
170
+ {
171
+ id: "cost-template-agent",
172
+ project_source: "template",
173
+ mode: "agent",
174
+ text: "Review this template for cost drivers, expensive defaults, and safer sizing choices.",
175
+ weight: 1
176
+ },
177
+ {
178
+ id: "cost-sync-agent",
179
+ project_source: "sync",
180
+ mode: "agent",
181
+ text: "Review this live environment for waste signals, savings opportunities, and ownership gaps.",
182
+ weight: 1
183
+ }
184
+ ],
185
+ system_instructions: "Use the Cost lens. Prioritize spend drivers, over-provisioning, idle or low-value resources, tagging gaps, pricing model choices, and savings actions. Keep recommendations commercially practical.",
186
+ default_settings: { ...baseSettings },
187
+ required_capabilities: [
188
+ "projects:read",
189
+ "reports:read",
190
+ "billing:read",
191
+ "ask:run"
192
+ ],
193
+ allowed_toolsets: ["projects", "reports", "billing", "cost"],
194
+ output_contract: {
195
+ sections: ["cost_drivers", "waste_signals", "savings_actions", "checkpoint"]
196
+ }
197
+ },
198
+ {
199
+ id: "triage",
200
+ display_name: "Triage",
201
+ description: "Helps investigate likely failure domains, affected resources, and first response actions.",
202
+ personality: "Calm, diagnostic, and time-sensitive.",
203
+ accent_key: "orange",
204
+ icon_key: "activity",
205
+ default_mode: "agent",
206
+ starter_prompt: "Triage this project for likely failure domains, impact paths, and first checks.",
207
+ starter_prompts: {
208
+ template: "Triage this template for likely deployment failure domains and first checks.",
209
+ sync: "Triage this live environment for likely failure domains, impacted paths, and containment checks."
210
+ },
211
+ starter_prompt_variants: [
212
+ {
213
+ id: "triage-template-agent",
214
+ project_source: "template",
215
+ mode: "agent",
216
+ text: "Triage this template for likely deployment failure domains and first checks.",
217
+ weight: 1
218
+ },
219
+ {
220
+ id: "triage-sync-agent",
221
+ project_source: "sync",
222
+ mode: "agent",
223
+ text: "Triage this live environment for likely failure domains, impacted paths, and containment checks.",
224
+ weight: 1
225
+ }
226
+ ],
227
+ system_instructions: "Use the Triage lens. Prioritize failure domains, affected resources, impact paths, first checks, rollback or containment options, and uncertainty that needs quick validation.",
228
+ default_settings: { ...baseSettings },
229
+ required_capabilities: ["projects:read", "reports:read", "ask:run"],
230
+ allowed_toolsets: ["projects", "reports", "graph", "rules"],
231
+ output_contract: {
232
+ sections: ["hypothesis", "impact", "first_checks", "containment"]
233
+ }
234
+ },
235
+ {
236
+ id: "remediation",
237
+ display_name: "Remediation",
238
+ description: "Turns findings into ordered implementation steps with owners, dependencies, and validation checks.",
239
+ personality: "Delivery-focused, practical, and sequencing-aware.",
240
+ accent_key: "rose",
241
+ icon_key: "list-checks",
242
+ default_mode: "agent",
243
+ starter_prompt: "Create an ordered remediation plan for this project with dependencies and validation checks.",
244
+ starter_prompts: {
245
+ template: "Create a template remediation plan with ordered changes, dependencies, and validation checks.",
246
+ sync: "Create a live-environment remediation plan with owners, dependencies, and validation checks."
247
+ },
248
+ starter_prompt_variants: [
249
+ {
250
+ id: "remediation-template-agent",
251
+ project_source: "template",
252
+ mode: "agent",
253
+ text: "Create a template remediation plan with ordered changes, dependencies, and validation checks.",
254
+ weight: 1
255
+ },
256
+ {
257
+ id: "remediation-sync-agent",
258
+ project_source: "sync",
259
+ mode: "agent",
260
+ text: "Create a live-environment remediation plan with owners, dependencies, and validation checks.",
261
+ weight: 1
262
+ }
263
+ ],
264
+ system_instructions: "Use the Remediation lens. Convert evidence into ordered fixes, owners, dependencies, risk sequencing, validation checks, rollback notes, and measurable completion criteria.",
265
+ default_settings: { ...baseSettings },
266
+ required_capabilities: ["projects:read", "reports:read", "ask:run"],
267
+ allowed_toolsets: ["projects", "reports", "rules"],
268
+ output_contract: {
269
+ sections: ["plan", "dependencies", "validation", "rollback"]
270
+ }
271
+ }
272
+ ];
273
+ var getBundledAgentProfiles = () => BUNDLED_AGENT_PROFILES.map((profile) => ({
274
+ ...profile,
275
+ starter_prompts: { ...profile.starter_prompts ?? {} },
276
+ starter_prompt_variants: profile.starter_prompt_variants?.map((variant) => ({
277
+ ...variant
278
+ })),
279
+ default_settings: { ...profile.default_settings },
280
+ required_capabilities: [...profile.required_capabilities],
281
+ allowed_toolsets: [...profile.allowed_toolsets],
282
+ output_contract: profile.output_contract ? { ...profile.output_contract } : void 0
283
+ }));
284
+ var getBundledAgentProfile = (profileId) => getBundledAgentProfiles().find((profile) => profile.id === profileId);
285
+
286
+ // ../core/dist/index.js
287
+ import fs from "fs";
288
+ import os from "os";
289
+ import path from "path";
290
+ import { execFileSync, spawn, spawnSync } from "child_process";
291
+ import { randomUUID } from "crypto";
292
+ import { randomUUID as randomUUID2 } from "crypto";
293
+ var DEFAULT_BASE_URL = "https://cloudeval.ai/api/proxy/v1";
294
+ var DEFAULT_FRONTEND_URL = "https://cloudeval.ai";
295
+ var TOKEN_EXPIRY_SKEW_MS = 12e4;
296
+ var ACCESS_SECRET_LABEL = "access-token";
297
+ var REFRESH_SECRET_LABEL = "refresh-token";
298
+ var INSECURE_FILE_FALLBACK_ENV = "CLOUDEVAL_ALLOW_INSECURE_FILE_STORAGE";
299
+ var CONCURRENT_REFRESH_WAIT_STEPS_MS = [50, 100, 150, 250];
300
+ var REFRESH_LOCK_WAIT_STEP_MS = 100;
301
+ var REFRESH_LOCK_STALE_MS = 3e4;
302
+ var CLI_DEBUG_ENV = "CLOUDEVAL_CLI_DEBUG";
303
+ var SENSITIVE_DEBUG_KEY_PATTERN = /token|authorization|cookie|secret|password|api[_-]?key|access[_-]?key|client_secret|refresh|device[_-]?code|user[_-]?code/i;
304
+ var SENSITIVE_DEBUG_QUERY_PARAM_PATTERN = /token|authorization|cookie|secret|password|api[_-]?key|access[_-]?key|client_secret|refresh|device[_-]?code|user[_-]?code|code/i;
305
+ var KEYCHAIN_SERVICE = "cloudeval-cli";
306
+ var KEYCHAIN_LABEL = "Cloudeval CLI";
307
+ var isCliDebugEnabled = () => {
308
+ const value = process.env[CLI_DEBUG_ENV];
309
+ return value === "1" || value?.toLowerCase() === "true";
310
+ };
311
+ var redactDebugValue = (value) => {
312
+ if (Array.isArray(value)) {
313
+ return value.map((item) => redactDebugValue(item));
314
+ }
315
+ if (typeof value === "string") {
316
+ try {
317
+ const url = new URL(value);
318
+ let changed = false;
319
+ for (const key of Array.from(url.searchParams.keys())) {
320
+ if (SENSITIVE_DEBUG_QUERY_PARAM_PATTERN.test(key)) {
321
+ url.searchParams.set(key, "[REDACTED]");
322
+ changed = true;
323
+ }
324
+ }
325
+ return redactSensitiveText(changed ? url.toString() : value);
326
+ } catch {
327
+ return redactSensitiveText(value);
328
+ }
329
+ }
330
+ if (value && typeof value === "object") {
331
+ const redacted = {};
332
+ for (const [key, item] of Object.entries(value)) {
333
+ redacted[key] = SENSITIVE_DEBUG_KEY_PATTERN.test(key) ? "[REDACTED]" : redactDebugValue(item);
334
+ }
335
+ return redacted;
336
+ }
337
+ return value;
338
+ };
339
+ var cliDebug = (message, data) => {
340
+ if (!isCliDebugEnabled()) {
341
+ return;
342
+ }
343
+ const prefix = `[${(/* @__PURE__ */ new Date()).toISOString()}] [CLI DEBUG]`;
344
+ if (data === void 0) {
345
+ console.error(`${prefix} ${message}`);
346
+ return;
347
+ }
348
+ try {
349
+ console.error(
350
+ `${prefix} ${message}
351
+ ${JSON.stringify(redactDebugValue(data), null, 2)}`
352
+ );
353
+ } catch {
354
+ console.error(`${prefix} ${message}`, redactDebugValue(data));
355
+ }
356
+ };
357
+ var cachedToken = null;
358
+ var stored = null;
359
+ var refreshInFlight = null;
360
+ var warnedAboutSecretBackend = false;
361
+ var memorySecrets = /* @__PURE__ */ new Map();
362
+ var now = () => Date.now();
363
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
364
+ var errorMessage = (error) => error instanceof Error ? error.message : typeof error === "string" ? error : String(error ?? "Unknown error");
365
+ var isRejectedRefreshTokenError = (error) => {
366
+ const message = errorMessage(error).toLowerCase();
367
+ return message.includes("invalid_grant") || message.includes("consent_required") || message.includes("aadsts65001") || message.includes("interaction_required") || message.includes("refresh token") && (message.includes("revoked") || message.includes("expired") || message.includes("invalid")) || message.includes("aadsts700082") || message.includes("aadsts70000");
368
+ };
369
+ var isProcessAlive = (pid) => {
370
+ if (!Number.isInteger(pid) || pid <= 0) {
371
+ return false;
372
+ }
373
+ try {
374
+ process.kill(pid, 0);
375
+ return true;
376
+ } catch (error) {
377
+ return error?.code === "EPERM";
378
+ }
379
+ };
380
+ var configDir = path.join(os.homedir(), ".config", "cloudeval");
381
+ var configPath = path.join(configDir, "config.json");
382
+ var secretFilePath = path.join(configDir, "secrets.json");
383
+ var refreshLockPath = path.join(configDir, "refresh.lock");
384
+ var commandExists = (cmd) => {
385
+ const whichCmd = process.platform === "win32" ? "where" : "which";
386
+ try {
387
+ execFileSync(whichCmd, [cmd], { stdio: "ignore" });
388
+ return true;
389
+ } catch {
390
+ return false;
391
+ }
392
+ };
393
+ var detectSecretBackend = () => {
394
+ if (process.env[INSECURE_FILE_FALLBACK_ENV] === "1") {
395
+ return "insecure-file";
396
+ }
397
+ if (process.platform === "darwin" && commandExists("security")) {
398
+ return "macos-keychain";
399
+ }
400
+ if (process.platform === "linux" && commandExists("secret-tool")) {
401
+ return "linux-libsecret";
402
+ }
403
+ if (process.platform === "win32" && commandExists("powershell")) {
404
+ return "windows-dpapi";
405
+ }
406
+ return "memory";
407
+ };
408
+ var ensureConfigDir = () => {
409
+ fs.mkdirSync(configDir, { recursive: true, mode: 448 });
410
+ };
411
+ var acquireRefreshLock = async () => {
412
+ ensureConfigDir();
413
+ while (true) {
414
+ try {
415
+ const fd = fs.openSync(refreshLockPath, "wx", 384);
416
+ let released = false;
417
+ try {
418
+ fs.writeFileSync(fd, String(process.pid), { encoding: "utf8" });
419
+ } catch {
420
+ }
421
+ return () => {
422
+ if (released) {
423
+ return;
424
+ }
425
+ released = true;
426
+ try {
427
+ fs.closeSync(fd);
428
+ } catch {
429
+ }
430
+ try {
431
+ fs.unlinkSync(refreshLockPath);
432
+ } catch {
433
+ }
434
+ };
435
+ } catch (error) {
436
+ if (error?.code !== "EEXIST") {
437
+ throw error;
438
+ }
439
+ try {
440
+ const pid = Number(fs.readFileSync(refreshLockPath, "utf8").trim());
441
+ if (pid && !isProcessAlive(pid)) {
442
+ fs.unlinkSync(refreshLockPath);
443
+ continue;
444
+ }
445
+ } catch {
446
+ }
447
+ try {
448
+ const stat = fs.statSync(refreshLockPath);
449
+ if (now() - stat.mtimeMs > REFRESH_LOCK_STALE_MS) {
450
+ fs.unlinkSync(refreshLockPath);
451
+ continue;
452
+ }
453
+ } catch {
454
+ continue;
455
+ }
456
+ await sleep(REFRESH_LOCK_WAIT_STEP_MS);
457
+ }
458
+ }
459
+ };
460
+ var readSecretsFile = () => {
461
+ try {
462
+ const raw = fs.readFileSync(secretFilePath, "utf8");
463
+ const parsed = JSON.parse(raw);
464
+ return parsed && typeof parsed === "object" ? parsed : {};
465
+ } catch {
466
+ return {};
467
+ }
468
+ };
469
+ var writeSecretsFile = (secrets) => {
470
+ ensureConfigDir();
471
+ fs.writeFileSync(secretFilePath, JSON.stringify(secrets, null, 2), {
472
+ encoding: "utf8",
473
+ mode: 384
474
+ });
475
+ try {
476
+ fs.chmodSync(secretFilePath, 384);
477
+ } catch {
478
+ }
479
+ };
480
+ var dpapiProtect = (plainText) => {
481
+ const script = "[Convert]::ToBase64String([System.Security.Cryptography.ProtectedData]::Protect([Text.Encoding]::UTF8.GetBytes($env:CLOUDEVAL_SECRET), $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser))";
482
+ return execFileSync(
483
+ "powershell",
484
+ ["-NoProfile", "-NonInteractive", "-Command", script],
485
+ {
486
+ encoding: "utf8",
487
+ env: {
488
+ ...process.env,
489
+ CLOUDEVAL_SECRET: plainText
490
+ }
491
+ }
492
+ ).trim();
493
+ };
494
+ var dpapiUnprotect = (cipherTextB64) => {
495
+ const script = "$bytes=[System.Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String($env:CLOUDEVAL_SECRET_B64), $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser); [Text.Encoding]::UTF8.GetString($bytes)";
496
+ return execFileSync(
497
+ "powershell",
498
+ ["-NoProfile", "-NonInteractive", "-Command", script],
499
+ {
500
+ encoding: "utf8",
501
+ env: {
502
+ ...process.env,
503
+ CLOUDEVAL_SECRET_B64: cipherTextB64
504
+ }
505
+ }
506
+ ).trim();
507
+ };
508
+ var setSecret = (key, value) => {
509
+ const backend = detectSecretBackend();
510
+ try {
511
+ if (backend === "macos-keychain") {
512
+ execFileSync("security", [
513
+ "add-generic-password",
514
+ "-a",
515
+ key,
516
+ "-s",
517
+ KEYCHAIN_SERVICE,
518
+ "-l",
519
+ KEYCHAIN_LABEL,
520
+ "-w",
521
+ value,
522
+ "-U"
523
+ ]);
524
+ return true;
525
+ }
526
+ if (backend === "linux-libsecret") {
527
+ const result = spawnSync(
528
+ "secret-tool",
529
+ [
530
+ "store",
531
+ "--label",
532
+ KEYCHAIN_LABEL,
533
+ "service",
534
+ KEYCHAIN_SERVICE,
535
+ "account",
536
+ key
537
+ ],
538
+ {
539
+ input: value,
540
+ encoding: "utf8"
541
+ }
542
+ );
543
+ return result.status === 0;
544
+ }
545
+ if (backend === "windows-dpapi") {
546
+ const encrypted = dpapiProtect(value);
547
+ const secrets = readSecretsFile();
548
+ secrets[key] = encrypted;
549
+ writeSecretsFile(secrets);
550
+ return true;
551
+ }
552
+ if (backend === "insecure-file") {
553
+ const secrets = readSecretsFile();
554
+ secrets[key] = value;
555
+ writeSecretsFile(secrets);
556
+ return true;
557
+ }
558
+ memorySecrets.set(key, value);
559
+ return false;
560
+ } catch {
561
+ memorySecrets.set(key, value);
562
+ return false;
563
+ }
564
+ };
565
+ var getSecret = (key) => {
566
+ const backend = detectSecretBackend();
567
+ try {
568
+ if (backend === "macos-keychain") {
569
+ return execFileSync("security", [
570
+ "find-generic-password",
571
+ "-a",
572
+ key,
573
+ "-s",
574
+ KEYCHAIN_SERVICE,
575
+ "-w"
576
+ ], { encoding: "utf8" }).trim();
577
+ }
578
+ if (backend === "linux-libsecret") {
579
+ return execFileSync(
580
+ "secret-tool",
581
+ ["lookup", "service", KEYCHAIN_SERVICE, "account", key],
582
+ { encoding: "utf8" }
583
+ ).trim();
584
+ }
585
+ if (backend === "windows-dpapi") {
586
+ const secrets = readSecretsFile();
587
+ const encrypted = secrets[key];
588
+ if (!encrypted) {
589
+ return void 0;
590
+ }
591
+ return dpapiUnprotect(encrypted);
592
+ }
593
+ if (backend === "insecure-file") {
594
+ const secrets = readSecretsFile();
595
+ return secrets[key];
596
+ }
597
+ return memorySecrets.get(key);
598
+ } catch {
599
+ return memorySecrets.get(key);
600
+ }
601
+ };
602
+ var deleteSecret = (key) => {
603
+ const backend = detectSecretBackend();
604
+ try {
605
+ if (backend === "macos-keychain") {
606
+ execFileSync(
607
+ "security",
608
+ [
609
+ "delete-generic-password",
610
+ "-a",
611
+ key,
612
+ "-s",
613
+ KEYCHAIN_SERVICE
614
+ ],
615
+ { stdio: "ignore" }
616
+ );
617
+ } else if (backend === "linux-libsecret") {
618
+ execFileSync(
619
+ "secret-tool",
620
+ [
621
+ "clear",
622
+ "service",
623
+ KEYCHAIN_SERVICE,
624
+ "account",
625
+ key
626
+ ],
627
+ { stdio: "ignore" }
628
+ );
629
+ } else if (backend === "windows-dpapi" || backend === "insecure-file") {
630
+ const secrets = readSecretsFile();
631
+ if (secrets[key]) {
632
+ delete secrets[key];
633
+ writeSecretsFile(secrets);
634
+ }
635
+ }
636
+ } catch {
637
+ }
638
+ memorySecrets.delete(key);
639
+ };
640
+ var warnOnInsecureSecretStorage = () => {
641
+ if (warnedAboutSecretBackend) {
642
+ return;
643
+ }
644
+ warnedAboutSecretBackend = true;
645
+ const backend = detectSecretBackend();
646
+ if (backend === "memory") {
647
+ console.warn(
648
+ "Secure credential storage is unavailable on this system. Session refresh tokens will not persist across CLI restarts."
649
+ );
650
+ console.warn(
651
+ `To force file fallback (less secure), set ${INSECURE_FILE_FALLBACK_ENV}=1.`
652
+ );
653
+ } else if (backend === "insecure-file") {
654
+ console.warn(
655
+ `Using insecure file secret fallback because ${INSECURE_FILE_FALLBACK_ENV}=1 is set.`
656
+ );
657
+ }
658
+ };
659
+ var isLocalHostname = (hostname) => {
660
+ const lower = hostname.toLowerCase();
661
+ return lower === "localhost" || lower === "127.0.0.1" || lower === "::1" || lower === "[::1]";
662
+ };
663
+ var assertSecureBaseUrl = (rawBaseUrl) => {
664
+ let parsed;
665
+ try {
666
+ parsed = new URL(rawBaseUrl);
667
+ } catch {
668
+ throw new Error(`Invalid base URL: ${rawBaseUrl}`);
669
+ }
670
+ if (parsed.protocol === "https:") {
671
+ return;
672
+ }
673
+ if (parsed.protocol === "http:" && isLocalHostname(parsed.hostname)) {
674
+ return;
675
+ }
676
+ throw new Error(
677
+ `Refusing insecure base URL (${rawBaseUrl}). Use HTTPS for non-localhost endpoints.`
678
+ );
679
+ };
680
+ var normalizeApiBase = (baseUrl) => {
681
+ const raw = baseUrl || process.env.CLOUDEVAL_BASE_URL || DEFAULT_BASE_URL;
682
+ assertSecureBaseUrl(raw);
683
+ const trimmed = raw.replace(/\/+$/, "");
684
+ try {
685
+ const url = new URL(trimmed);
686
+ const hostname = url.hostname.toLowerCase();
687
+ const path2 = url.pathname.replace(/\/+$/, "") || "/";
688
+ if (hostname === "cloudeval.ai" && ["/", "/api", "/api/v1"].includes(path2)) {
689
+ url.pathname = "/api/proxy/v1";
690
+ url.search = "";
691
+ url.hash = "";
692
+ return url.toString().replace(/\/+$/, "");
693
+ }
694
+ } catch {
695
+ }
696
+ if (trimmed.endsWith("/api/v1") || trimmed.endsWith("/api/proxy/v1")) {
697
+ return trimmed;
698
+ }
699
+ if (trimmed.endsWith("/api/proxy")) {
700
+ return `${trimmed}/v1`;
701
+ }
702
+ return trimmed.replace(/\/api\/?$/, "") + "/api/v1";
703
+ };
704
+ var resolveAuthBootstrapBase = (apiBase) => {
705
+ try {
706
+ const url = new URL(apiBase);
707
+ const path2 = url.pathname.replace(/\/+$/, "");
708
+ if (url.hostname.toLowerCase() === "cloudeval.ai" && /\/api\/proxy\/v1$/i.test(path2)) {
709
+ url.pathname = path2.replace(/\/api\/proxy\/v1$/i, "/api/v1");
710
+ url.search = "";
711
+ url.hash = "";
712
+ return url.toString().replace(/\/+$/, "");
713
+ }
714
+ } catch {
715
+ }
716
+ return apiBase;
717
+ };
718
+ var sanitizeStoredForDisk = (data) => {
719
+ const clone = { ...data };
720
+ delete clone.token;
721
+ delete clone.refreshToken;
722
+ return clone;
723
+ };
724
+ var migrateLegacySecrets = (parsed) => {
725
+ const migrated = { ...parsed };
726
+ if (parsed.token && !parsed.tokenRef) {
727
+ const ref = ACCESS_SECRET_LABEL;
728
+ const persisted = setSecret(ref, parsed.token);
729
+ if (!persisted) {
730
+ warnOnInsecureSecretStorage();
731
+ }
732
+ migrated.tokenRef = ref;
733
+ delete migrated.token;
734
+ }
735
+ if (parsed.refreshToken && !parsed.refreshTokenRef) {
736
+ const ref = REFRESH_SECRET_LABEL;
737
+ const persisted = setSecret(ref, parsed.refreshToken);
738
+ if (!persisted) {
739
+ warnOnInsecureSecretStorage();
740
+ }
741
+ migrated.refreshTokenRef = ref;
742
+ delete migrated.refreshToken;
743
+ }
744
+ return migrated;
745
+ };
746
+ var loadStoredFromDisk = () => {
747
+ try {
748
+ const raw = fs.readFileSync(configPath, "utf8");
749
+ const parsed = JSON.parse(raw);
750
+ const nextStored = migrateLegacySecrets(parsed);
751
+ const accessToken = getAccessToken(nextStored);
752
+ if (accessToken) {
753
+ cachedToken = {
754
+ token: accessToken,
755
+ expiresAt: nextStored.tokenExpiresAt ?? 0
756
+ };
757
+ }
758
+ if (nextStored !== parsed) {
759
+ const sanitized = sanitizeStoredForDisk(nextStored);
760
+ fs.writeFileSync(configPath, JSON.stringify(sanitized, null, 2), {
761
+ encoding: "utf8",
762
+ mode: 384
763
+ });
764
+ }
765
+ return nextStored;
766
+ } catch {
767
+ return {};
768
+ }
769
+ };
770
+ var readStored = () => {
771
+ if (stored) {
772
+ return stored;
773
+ }
774
+ stored = loadStoredFromDisk();
775
+ return stored;
776
+ };
777
+ var reloadStored = () => {
778
+ stored = loadStoredFromDisk();
779
+ return stored;
780
+ };
781
+ var writeStored = (data) => {
782
+ const sanitized = sanitizeStoredForDisk(data);
783
+ try {
784
+ ensureConfigDir();
785
+ fs.writeFileSync(configPath, JSON.stringify(sanitized, null, 2), {
786
+ encoding: "utf8",
787
+ mode: 384
788
+ });
789
+ try {
790
+ fs.chmodSync(configPath, 384);
791
+ } catch {
792
+ }
793
+ stored = sanitized;
794
+ } catch {
795
+ stored = sanitized;
796
+ }
797
+ };
798
+ var getRefreshToken = (data) => {
799
+ if (data.refreshTokenRef) {
800
+ return getSecret(data.refreshTokenRef);
801
+ }
802
+ return void 0;
803
+ };
804
+ var getAccessToken = (data) => {
805
+ if (data.tokenRef) {
806
+ return getSecret(data.tokenRef);
807
+ }
808
+ return data.token;
809
+ };
810
+ var saveAccessToken = (data, accessToken) => {
811
+ const next = { ...data };
812
+ if (!accessToken) {
813
+ return next;
814
+ }
815
+ const ref = next.tokenRef || ACCESS_SECRET_LABEL;
816
+ const persisted = setSecret(ref, accessToken);
817
+ if (!persisted) {
818
+ warnOnInsecureSecretStorage();
819
+ }
820
+ next.tokenRef = ref;
821
+ return next;
822
+ };
823
+ var saveRefreshToken = (data, refreshToken) => {
824
+ const next = { ...data };
825
+ if (!refreshToken) {
826
+ return next;
827
+ }
828
+ const ref = next.refreshTokenRef || REFRESH_SECRET_LABEL;
829
+ const persisted = setSecret(ref, refreshToken);
830
+ if (!persisted) {
831
+ warnOnInsecureSecretStorage();
832
+ }
833
+ next.refreshTokenRef = ref;
834
+ return next;
835
+ };
836
+ var clearLocalAuth = (data) => {
837
+ cachedToken = null;
838
+ refreshInFlight = null;
839
+ const current = data ?? readStored();
840
+ if (current.tokenRef) {
841
+ deleteSecret(current.tokenRef);
842
+ }
843
+ if (current.refreshTokenRef) {
844
+ deleteSecret(current.refreshTokenRef);
845
+ }
846
+ stored = {};
847
+ writeStored({});
848
+ try {
849
+ if (fs.existsSync(configPath)) {
850
+ fs.unlinkSync(configPath);
851
+ }
852
+ } catch {
853
+ }
854
+ };
855
+ var clearAuthSession = () => {
856
+ clearLocalAuth();
857
+ };
858
+ var setCachedToken = (token, expiresInSeconds) => {
859
+ cachedToken = {
860
+ token,
861
+ expiresAt: now() + expiresInSeconds * 1e3 - TOKEN_EXPIRY_SKEW_MS
862
+ };
863
+ };
864
+ var persistAuthTokens = (tokenResponse, context) => {
865
+ setCachedToken(tokenResponse.access_token, tokenResponse.expires_in ?? 3600);
866
+ const current = readStored();
867
+ let next = {
868
+ ...current,
869
+ token: tokenResponse.access_token,
870
+ tokenExpiresAt: cachedToken?.expiresAt,
871
+ sessionId: tokenResponse.session_id ?? current.sessionId,
872
+ accountId: tokenResponse.account_id ?? current.accountId,
873
+ baseUrl: context.baseUrl,
874
+ lastRefreshAt: now()
875
+ };
876
+ next = saveAccessToken(next, tokenResponse.access_token);
877
+ next = saveRefreshToken(next, tokenResponse.refresh_token);
878
+ writeStored(next);
879
+ return tokenResponse.access_token;
880
+ };
881
+ var getCLIClientId = () => process.env.CLOUDEVAL_CLI_CLIENT_ID ?? "cloudeval-cli";
882
+ var getDeviceVerificationOverride = () => (process.env.CLOUDEVAL_DEVICE_VERIFICATION_URI || process.env.CLOUDEVAL_FRONTEND_URL || process.env.CLOUDEVAL_WEB_URL || "").trim();
883
+ var DEVICE_LOGIN_PROMPT = "select_account";
884
+ var forceDeviceLoginAccountChooser = (value) => {
885
+ const url = new URL(value);
886
+ url.searchParams.set("prompt", DEVICE_LOGIN_PROMPT);
887
+ return url.toString();
888
+ };
889
+ var buildDeviceVerificationUrl = (override, userCode) => {
890
+ const url = new URL(override);
891
+ if (!url.pathname || url.pathname === "/") {
892
+ url.pathname = "/device/login";
893
+ }
894
+ if (userCode) {
895
+ url.searchParams.set("user_code", userCode);
896
+ }
897
+ url.searchParams.set("prompt", DEVICE_LOGIN_PROMPT);
898
+ return url.toString();
899
+ };
900
+ var isLocalDeviceVerificationUrl = (value) => {
901
+ try {
902
+ const url = new URL(value);
903
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1";
904
+ } catch {
905
+ return false;
906
+ }
907
+ };
908
+ var resolveDeviceVerificationUrl = (deviceCodeData) => {
909
+ const override = getDeviceVerificationOverride();
910
+ if (override) {
911
+ try {
912
+ return buildDeviceVerificationUrl(override, deviceCodeData.user_code);
913
+ } catch {
914
+ console.warn(
915
+ `Ignoring invalid device verification URL override: ${override}`
916
+ );
917
+ }
918
+ }
919
+ const backendUrl = deviceCodeData.verification_uri_complete || (() => {
920
+ try {
921
+ return buildDeviceVerificationUrl(
922
+ deviceCodeData.verification_uri,
923
+ deviceCodeData.user_code
924
+ );
925
+ } catch {
926
+ return deviceCodeData.verification_uri;
927
+ }
928
+ })();
929
+ if (isLocalDeviceVerificationUrl(backendUrl)) {
930
+ return buildDeviceVerificationUrl(DEFAULT_FRONTEND_URL, deviceCodeData.user_code);
931
+ }
932
+ try {
933
+ return forceDeviceLoginAccountChooser(backendUrl);
934
+ } catch {
935
+ return backendUrl;
936
+ }
937
+ };
938
+ var openBrowser = (url) => {
939
+ try {
940
+ if (process.platform === "darwin") {
941
+ const child = spawn("open", [url], {
942
+ detached: true,
943
+ stdio: "ignore"
944
+ });
945
+ child.unref();
946
+ return true;
947
+ }
948
+ if (process.platform === "win32") {
949
+ const child = spawn("cmd", ["/c", "start", "", url], {
950
+ detached: true,
951
+ stdio: "ignore"
952
+ });
953
+ child.unref();
954
+ return true;
955
+ }
956
+ if (commandExists("xdg-open")) {
957
+ const child = spawn("xdg-open", [url], {
958
+ detached: true,
959
+ stdio: "ignore"
960
+ });
961
+ child.unref();
962
+ return true;
963
+ }
964
+ return false;
965
+ } catch {
966
+ return false;
967
+ }
968
+ };
969
+ var PLAYGROUND_PROJECT_NAME = "Playground";
970
+ var getCLIHeaders = (token) => {
971
+ const headers = {
972
+ "X-Client-Type": "cloudeval-cli",
973
+ "X-Client-Version": "0.1.0",
974
+ "Content-Type": "application/json"
975
+ };
976
+ if (token) {
977
+ headers.Authorization = `Bearer ${token}`;
978
+ }
979
+ return headers;
980
+ };
981
+ var getProjects = async (baseUrl, token, userId) => {
982
+ try {
983
+ const apiBase = normalizeApiBase(baseUrl);
984
+ cliDebug("getProjects request", {
985
+ url: `${apiBase}/projects/user/${userId}`,
986
+ userId
987
+ });
988
+ const response = await fetch(`${apiBase}/projects/user/${userId}`, {
989
+ method: "GET",
990
+ headers: getCLIHeaders(token)
991
+ });
992
+ cliDebug("getProjects response", {
993
+ status: response.status,
994
+ ok: response.ok
995
+ });
996
+ if (!response.ok) {
997
+ if (response.status === 404) {
998
+ return [];
999
+ }
1000
+ throw new Error(`Failed to fetch projects: ${response.status}`);
1001
+ }
1002
+ const projects = await response.json();
1003
+ const parsedProjects = Array.isArray(projects) ? projects : [];
1004
+ cliDebug("getProjects parsed", {
1005
+ count: parsedProjects.length,
1006
+ names: parsedProjects.map((project) => project?.name)
1007
+ });
1008
+ return parsedProjects;
1009
+ } catch (error) {
1010
+ cliDebug("getProjects failed", {
1011
+ message: error?.message
1012
+ });
1013
+ console.warn("Failed to fetch projects:", error.message);
1014
+ return [];
1015
+ }
1016
+ };
1017
+ var getAccessibleProjects = async (baseUrl, token) => {
1018
+ const apiBase = normalizeApiBase(baseUrl);
1019
+ cliDebug("getAccessibleProjects request", {
1020
+ url: `${apiBase}/projects/`
1021
+ });
1022
+ const response = await fetch(`${apiBase}/projects/`, {
1023
+ method: "GET",
1024
+ headers: getCLIHeaders(token)
1025
+ });
1026
+ cliDebug("getAccessibleProjects response", {
1027
+ status: response.status,
1028
+ ok: response.ok
1029
+ });
1030
+ if (!response.ok) {
1031
+ if (response.status === 404) {
1032
+ return [];
1033
+ }
1034
+ throw new Error(`Failed to fetch projects: ${response.status}`);
1035
+ }
1036
+ const projects = await response.json();
1037
+ return Array.isArray(projects) ? projects : [];
1038
+ };
1039
+ var getPlaygroundProject = (projects) => projects.find((project) => project.name === PLAYGROUND_PROJECT_NAME);
1040
+ var getUserDisplayName = (user) => user.fullName || user.full_name || user.name;
1041
+ var quickOnboardPlayground = async (baseUrl, token, user, extraPayload = {}) => {
1042
+ if (!user.email) {
1043
+ throw new Error(
1044
+ "Playground project is missing and the authenticated user email is unavailable. Please login again."
1045
+ );
1046
+ }
1047
+ const apiBase = normalizeApiBase(baseUrl);
1048
+ const fullName = getUserDisplayName(user);
1049
+ const body = {
1050
+ email: user.email,
1051
+ ...fullName ? { full_name: fullName } : {},
1052
+ ...extraPayload
1053
+ };
1054
+ cliDebug("quickOnboardPlayground request", {
1055
+ url: `${apiBase}/onboard/quick`,
1056
+ email: user.email,
1057
+ hasFullName: !!fullName,
1058
+ payloadKeys: Object.keys(body)
1059
+ });
1060
+ const response = await fetch(`${apiBase}/onboard/quick`, {
1061
+ method: "POST",
1062
+ headers: getCLIHeaders(token),
1063
+ body: JSON.stringify(body)
1064
+ });
1065
+ cliDebug("quickOnboardPlayground response", {
1066
+ status: response.status,
1067
+ ok: response.ok
1068
+ });
1069
+ if (!response.ok) {
1070
+ const detail = await readResponseDetail(response);
1071
+ cliDebug("quickOnboardPlayground failed", {
1072
+ status: response.status,
1073
+ detail
1074
+ });
1075
+ throw new Error(
1076
+ `Failed to run shared Playground onboarding: ${response.status} ${response.statusText}${detail ? ` - ${detail}` : ""}`
1077
+ );
1078
+ }
1079
+ const data = await response.json();
1080
+ cliDebug("quickOnboardPlayground parsed", {
1081
+ userId: data.user?.id,
1082
+ hasPlaygroundProject: !!data.playground_project?.id,
1083
+ hasDefaultConnection: !!data.default_connection?.id,
1084
+ setupJobs: data.setup_jobs?.map((job) => job.operation)
1085
+ });
1086
+ return data;
1087
+ };
1088
+ var ensurePlaygroundProject = async (baseUrl, token, user, options = {}) => {
1089
+ cliDebug("ensurePlaygroundProject start", {
1090
+ userId: user.id,
1091
+ email: user.email,
1092
+ forceQuickOnboard: !!options.forceQuickOnboard
1093
+ });
1094
+ const existingProjects = await getProjects(baseUrl, token, user.id);
1095
+ const existingPlayground = getPlaygroundProject(existingProjects);
1096
+ if (existingPlayground && !options.forceQuickOnboard) {
1097
+ cliDebug("ensurePlaygroundProject existing Playground found", {
1098
+ projectId: existingPlayground.id
1099
+ });
1100
+ return existingPlayground;
1101
+ }
1102
+ const onboardResponse = await quickOnboardPlayground(baseUrl, token, user);
1103
+ if (onboardResponse.playground_project?.id) {
1104
+ cliDebug("ensurePlaygroundProject repaired from quick response", {
1105
+ projectId: onboardResponse.playground_project.id
1106
+ });
1107
+ return onboardResponse.playground_project;
1108
+ }
1109
+ const refreshedProjects = await getProjects(baseUrl, token, user.id);
1110
+ const refreshedPlayground = getPlaygroundProject(refreshedProjects);
1111
+ if (refreshedPlayground) {
1112
+ cliDebug("ensurePlaygroundProject repaired after project refetch", {
1113
+ projectId: refreshedPlayground.id
1114
+ });
1115
+ return refreshedPlayground;
1116
+ }
1117
+ throw new Error(
1118
+ "Shared onboarding completed, but no Playground project was returned or found."
1119
+ );
1120
+ };
1121
+ var ensureDefaultProject = async (baseUrl, token, user) => ensurePlaygroundProject(
1122
+ baseUrl,
1123
+ token,
1124
+ typeof user === "string" ? { id: user } : user
1125
+ );
1126
+ var readResponseDetail = async (response) => {
1127
+ try {
1128
+ const raw = await response.text();
1129
+ if (!raw || !raw.trim()) {
1130
+ return void 0;
1131
+ }
1132
+ const contentType = response.headers.get("content-type") ?? "";
1133
+ if (contentType.includes("text/html") || /^\s*</.test(raw)) {
1134
+ return "backend returned an HTML error page; check --base-url/CLOUDEVAL_BASE_URL and backend health";
1135
+ }
1136
+ try {
1137
+ const json = JSON.parse(raw);
1138
+ return json.message || json.error_description || json.error || json.detail || raw;
1139
+ } catch {
1140
+ return raw;
1141
+ }
1142
+ } catch {
1143
+ return void 0;
1144
+ }
1145
+ };
1146
+ var loginWithDeviceCode = async (baseUrl, options = {}) => {
1147
+ const apiBase = normalizeApiBase(baseUrl);
1148
+ const authBase = resolveAuthBootstrapBase(apiBase);
1149
+ const clientId = getCLIClientId();
1150
+ const requestBody = JSON.stringify({ client_id: clientId });
1151
+ cliDebug("backend device-code request", {
1152
+ url: `${authBase}/auth/device/code`,
1153
+ clientId
1154
+ });
1155
+ const deviceCodeResponse = await fetch(`${authBase}/auth/device/code`, {
1156
+ method: "POST",
1157
+ headers: getCLIHeaders(),
1158
+ body: requestBody
1159
+ });
1160
+ cliDebug("backend device-code response", {
1161
+ status: deviceCodeResponse.status,
1162
+ ok: deviceCodeResponse.ok
1163
+ });
1164
+ if (!deviceCodeResponse.ok) {
1165
+ const statusInfo = `${deviceCodeResponse.status} ${deviceCodeResponse.statusText}`;
1166
+ const detail = await readResponseDetail(deviceCodeResponse);
1167
+ if (deviceCodeResponse.status === 401 || deviceCodeResponse.status === 403) {
1168
+ throw new Error(
1169
+ `CloudEval backend device-code login is blocked by an authentication layer (${statusInfo}${detail ? ` - ${detail}` : ""}). The CLI needs /api/v1/auth/device/code to stay public so it can show the CloudEval approval URL.`
1170
+ );
1171
+ }
1172
+ let errorMessage2 = `Failed to initiate login: ${statusInfo}`;
1173
+ if (detail) {
1174
+ errorMessage2 = `Failed to initiate login: ${statusInfo} - ${detail}`;
1175
+ }
1176
+ throw new Error(errorMessage2);
1177
+ }
1178
+ return pollDeviceCodeAndPersist(authBase, clientId, deviceCodeResponse, {
1179
+ openInBrowser: options.openInBrowser,
1180
+ browserOpener: options.browserOpener,
1181
+ persistBaseUrl: apiBase
1182
+ });
1183
+ };
1184
+ var pollDeviceCodeAndPersist = async (apiBase, clientId, deviceCodeResponse, options = {}) => {
1185
+ const deviceCodeData = await deviceCodeResponse.json();
1186
+ const verificationUrl = resolveDeviceVerificationUrl(deviceCodeData);
1187
+ const browserOpener = options.browserOpener ?? openBrowser;
1188
+ let openedInBrowser = false;
1189
+ cliDebug("backend device-code parsed", {
1190
+ hasVerificationUriComplete: !!deviceCodeData.verification_uri_complete,
1191
+ verificationUrl,
1192
+ userCode: deviceCodeData.user_code,
1193
+ expiresIn: deviceCodeData.expires_in,
1194
+ interval: deviceCodeData.interval
1195
+ });
1196
+ if (options.openInBrowser && verificationUrl) {
1197
+ console.log("\nOpening browser for authentication...");
1198
+ openedInBrowser = browserOpener(verificationUrl);
1199
+ console.log(`Approval URL: ${verificationUrl}`);
1200
+ }
1201
+ if (!openedInBrowser) {
1202
+ console.log("\nTo sign in, use a web browser to open:");
1203
+ console.log(` ${verificationUrl}`);
1204
+ }
1205
+ console.log(
1206
+ `
1207
+ ${openedInBrowser ? "If prompted, enter code" : "Enter code"}: ${deviceCodeData.user_code}
1208
+ `
1209
+ );
1210
+ console.log("Waiting for authentication...");
1211
+ process.stdout.write(" ");
1212
+ const startTime = now();
1213
+ const expiresAt = startTime + deviceCodeData.expires_in * 1e3;
1214
+ let intervalMs = Math.max(1, deviceCodeData.interval || 5) * 1e3;
1215
+ while (now() < expiresAt) {
1216
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
1217
+ const tokenResponse = await fetch(`${apiBase}/auth/device/token`, {
1218
+ method: "POST",
1219
+ headers: getCLIHeaders(),
1220
+ body: JSON.stringify({
1221
+ device_code: deviceCodeData.device_code,
1222
+ client_id: clientId
1223
+ })
1224
+ });
1225
+ cliDebug("backend device-token response", {
1226
+ status: tokenResponse.status,
1227
+ ok: tokenResponse.ok
1228
+ });
1229
+ const tokenResponseForDetail = tokenResponse.clone();
1230
+ let tokenData;
1231
+ try {
1232
+ tokenData = await tokenResponse.json();
1233
+ } catch {
1234
+ const detail = await readResponseDetail(tokenResponseForDetail);
1235
+ throw new Error(
1236
+ `Device token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}${detail ? ` - ${detail}` : ""}`
1237
+ );
1238
+ }
1239
+ if (tokenResponse.ok && tokenData.access_token) {
1240
+ const accessToken = persistAuthTokens(tokenData, {
1241
+ baseUrl: options.persistBaseUrl ?? apiBase
1242
+ });
1243
+ cliDebug("backend device-token completed", {
1244
+ hasRefreshToken: !!tokenData.refresh_token,
1245
+ sessionId: tokenData.session_id,
1246
+ accountId: tokenData.account_id
1247
+ });
1248
+ console.log("\nAuthentication successful. Session saved.\n");
1249
+ return accessToken;
1250
+ }
1251
+ if (tokenData.error === "authorization_pending") {
1252
+ process.stdout.write(".");
1253
+ continue;
1254
+ }
1255
+ if (tokenData.error === "slow_down" && tokenData.interval) {
1256
+ intervalMs = tokenData.interval * 1e3;
1257
+ continue;
1258
+ }
1259
+ if (tokenData.error) {
1260
+ throw new Error(tokenData.error);
1261
+ }
1262
+ }
1263
+ throw new Error("Authentication timeout. Please try again.");
1264
+ };
1265
+ var login = async (baseUrl, options = {}) => {
1266
+ return loginWithDeviceCode(baseUrl, {
1267
+ openInBrowser: !options.headless,
1268
+ browserOpener: options.browserOpener
1269
+ });
1270
+ };
1271
+ var refreshViaBackend = async (apiBase, refreshToken) => {
1272
+ const authBase = resolveAuthBootstrapBase(apiBase);
1273
+ const response = await fetch(`${authBase}/auth/refresh`, {
1274
+ method: "POST",
1275
+ headers: getCLIHeaders(),
1276
+ body: JSON.stringify({
1277
+ refresh_token: refreshToken,
1278
+ client_id: getCLIClientId()
1279
+ })
1280
+ });
1281
+ if (response.status === 404 || response.status === 405) {
1282
+ return null;
1283
+ }
1284
+ if (!response.ok) {
1285
+ const errorText = await response.text();
1286
+ const lowerErrorText = errorText.toLowerCase();
1287
+ if ((response.status === 401 || response.status === 403) && (lowerErrorText.includes("auth_required_public") || lowerErrorText.includes("authentication required for this endpoint"))) {
1288
+ return null;
1289
+ }
1290
+ throw new Error(errorText || "Token refresh failed");
1291
+ }
1292
+ return await response.json();
1293
+ };
1294
+ var refreshAuthToken = async (refreshToken, baseUrl) => {
1295
+ const apiBase = normalizeApiBase(baseUrl);
1296
+ const backendResponse = await refreshViaBackend(apiBase, refreshToken);
1297
+ if (backendResponse) {
1298
+ return backendResponse;
1299
+ }
1300
+ throw new Error(
1301
+ "Token refresh unavailable from CloudEval backend. Run 'cloudeval login' and retry."
1302
+ );
1303
+ };
1304
+ var waitForConcurrentRefreshToken = async (previousRefreshToken) => {
1305
+ for (const delayMs of CONCURRENT_REFRESH_WAIT_STEPS_MS) {
1306
+ await sleep(delayMs);
1307
+ const latest2 = reloadStored();
1308
+ const latestRefreshToken2 = getRefreshToken(latest2);
1309
+ if (latestRefreshToken2 && latestRefreshToken2 !== previousRefreshToken) {
1310
+ return latestRefreshToken2;
1311
+ }
1312
+ }
1313
+ const latest = reloadStored();
1314
+ const latestRefreshToken = getRefreshToken(latest);
1315
+ if (latestRefreshToken && latestRefreshToken !== previousRefreshToken) {
1316
+ return latestRefreshToken;
1317
+ }
1318
+ return void 0;
1319
+ };
1320
+ var resolveRefreshBaseUrl = (requestedBaseUrl, storedBaseUrl) => {
1321
+ if (!requestedBaseUrl) {
1322
+ return storedBaseUrl;
1323
+ }
1324
+ if (storedBaseUrl && normalizeApiBase(requestedBaseUrl) === DEFAULT_BASE_URL) {
1325
+ return storedBaseUrl;
1326
+ }
1327
+ return requestedBaseUrl;
1328
+ };
1329
+ var performRefresh = async (options) => {
1330
+ const disk = readStored();
1331
+ const refreshToken = getRefreshToken(disk);
1332
+ if (!refreshToken) {
1333
+ throw new Error("No refresh token available. Please run 'cloudeval login'.");
1334
+ }
1335
+ const refreshBaseUrl = resolveRefreshBaseUrl(options.baseUrl, disk.baseUrl);
1336
+ const finishRefresh = async (currentRefreshToken, currentBaseUrl) => {
1337
+ const refreshed = await refreshAuthToken(currentRefreshToken, currentBaseUrl);
1338
+ if (!refreshed.access_token) {
1339
+ throw new Error("Token refresh response missing access_token.");
1340
+ }
1341
+ return persistAuthTokens(refreshed, {
1342
+ baseUrl: normalizeApiBase(currentBaseUrl)
1343
+ });
1344
+ };
1345
+ try {
1346
+ return await finishRefresh(refreshToken, refreshBaseUrl);
1347
+ } catch (error) {
1348
+ let latest = reloadStored();
1349
+ let latestRefreshToken = getRefreshToken(latest);
1350
+ if (!latestRefreshToken || latestRefreshToken === refreshToken) {
1351
+ const waitedRefreshToken = await waitForConcurrentRefreshToken(refreshToken);
1352
+ if (waitedRefreshToken) {
1353
+ latest = reloadStored();
1354
+ latestRefreshToken = waitedRefreshToken;
1355
+ }
1356
+ }
1357
+ if (latestRefreshToken && latestRefreshToken !== refreshToken) {
1358
+ const latestBaseUrl = resolveRefreshBaseUrl(options.baseUrl, latest.baseUrl) || refreshBaseUrl;
1359
+ return finishRefresh(latestRefreshToken, latestBaseUrl);
1360
+ }
1361
+ throw error;
1362
+ }
1363
+ };
1364
+ var refreshWithSingleFlight = async (options) => {
1365
+ if (refreshInFlight) {
1366
+ return refreshInFlight;
1367
+ }
1368
+ refreshInFlight = (async () => {
1369
+ const releaseRefreshLock = await acquireRefreshLock();
1370
+ try {
1371
+ return await performRefresh(options);
1372
+ } finally {
1373
+ releaseRefreshLock();
1374
+ refreshInFlight = null;
1375
+ }
1376
+ })();
1377
+ return refreshInFlight;
1378
+ };
1379
+ var logout = async (options = {}) => {
1380
+ const disk = readStored();
1381
+ const refreshToken = getRefreshToken(disk);
1382
+ const currentToken = cachedToken?.token || getAccessToken(disk);
1383
+ let revoked = false;
1384
+ if (refreshToken) {
1385
+ try {
1386
+ const apiBase = normalizeApiBase(options.baseUrl || disk.baseUrl);
1387
+ const endpoint = options.allDevices ? "/auth/logout-all" : "/auth/logout";
1388
+ const response = await fetch(`${apiBase}${endpoint}`, {
1389
+ method: "POST",
1390
+ headers: getCLIHeaders(currentToken),
1391
+ body: JSON.stringify({
1392
+ refresh_token: refreshToken,
1393
+ session_id: disk.sessionId
1394
+ })
1395
+ });
1396
+ if (response.ok || response.status === 404 || response.status === 405) {
1397
+ revoked = response.ok;
1398
+ }
1399
+ } catch {
1400
+ }
1401
+ }
1402
+ clearLocalAuth(disk);
1403
+ return { revoked, localCleared: true };
1404
+ };
1405
+ var getAuthStatus = async (baseUrl, options = {}) => {
1406
+ let disk = readStored();
1407
+ let refreshToken = getRefreshToken(disk);
1408
+ let accessToken = getAccessToken(disk);
1409
+ let accessTokenCached = Boolean(
1410
+ cachedToken && cachedToken.expiresAt > now() || accessToken && disk.tokenExpiresAt && disk.tokenExpiresAt > now()
1411
+ );
1412
+ let authenticated = Boolean(accessTokenCached || refreshToken);
1413
+ let authError;
1414
+ if (options.validate && authenticated) {
1415
+ try {
1416
+ const validationBaseUrl = resolveRefreshBaseUrl(baseUrl, disk.baseUrl);
1417
+ const token = await getAuthToken({ baseUrl: validationBaseUrl });
1418
+ if (validationBaseUrl) {
1419
+ await checkUserStatus(validationBaseUrl, token);
1420
+ }
1421
+ disk = readStored();
1422
+ refreshToken = getRefreshToken(disk);
1423
+ accessToken = getAccessToken(disk);
1424
+ accessTokenCached = Boolean(
1425
+ cachedToken && cachedToken.expiresAt > now() || accessToken && disk.tokenExpiresAt && disk.tokenExpiresAt > now()
1426
+ );
1427
+ authenticated = true;
1428
+ } catch (error) {
1429
+ authError = errorMessage(error);
1430
+ disk = readStored();
1431
+ refreshToken = getRefreshToken(disk);
1432
+ accessToken = getAccessToken(disk);
1433
+ accessTokenCached = Boolean(
1434
+ cachedToken && cachedToken.expiresAt > now() || accessToken && disk.tokenExpiresAt && disk.tokenExpiresAt > now()
1435
+ );
1436
+ authenticated = false;
1437
+ }
1438
+ }
1439
+ return {
1440
+ authenticated,
1441
+ accessTokenCached,
1442
+ accessTokenExpiresAt: cachedToken?.expiresAt ?? disk.tokenExpiresAt,
1443
+ hasRefreshToken: Boolean(refreshToken),
1444
+ sessionId: disk.sessionId,
1445
+ accountId: disk.accountId,
1446
+ baseUrl: disk.baseUrl || baseUrl,
1447
+ storageBackend: detectSecretBackend(),
1448
+ validationAttempted: options.validate,
1449
+ authError
1450
+ };
1451
+ };
1452
+ var extractEmailFromToken = (token) => {
1453
+ try {
1454
+ const parts = token.split(".");
1455
+ if (parts.length !== 3) return null;
1456
+ const payload = JSON.parse(
1457
+ Buffer.from(parts[1], "base64url").toString("utf-8")
1458
+ );
1459
+ return payload.email || payload.upn || payload.preferred_username || null;
1460
+ } catch {
1461
+ return null;
1462
+ }
1463
+ };
1464
+ var AUTH_LOOKUP_ERROR = "CloudEvalAuthLookupError";
1465
+ var authLookupError = async (response, context) => {
1466
+ let detail = "";
1467
+ try {
1468
+ const body = await response.text();
1469
+ if (body) {
1470
+ detail = ` - ${body.slice(0, 300)}`;
1471
+ }
1472
+ } catch {
1473
+ }
1474
+ const error = new Error(`${context} failed: ${response.status} ${response.statusText}${detail}`);
1475
+ error.name = AUTH_LOOKUP_ERROR;
1476
+ return error;
1477
+ };
1478
+ var isAuthLookupError = (error) => error instanceof Error && error.name === AUTH_LOOKUP_ERROR;
1479
+ var isAuthLookupFailure = (error) => isAuthLookupError(error);
1480
+ var clearStoredAuthIfTokenMatches = (token) => {
1481
+ const disk = readStored();
1482
+ const diskAccessToken = getAccessToken(disk);
1483
+ if (cachedToken?.token === token || diskAccessToken === token) {
1484
+ clearLocalAuth(disk);
1485
+ return true;
1486
+ }
1487
+ return false;
1488
+ };
1489
+ var fetchCurrentUserFromServer = async (apiBase, token) => {
1490
+ try {
1491
+ const startedAt = Date.now();
1492
+ cliDebug("fetchCurrentUserFromServer request", {
1493
+ url: `${apiBase}/auth/me`
1494
+ });
1495
+ const response = await fetch(`${apiBase}/auth/me`, {
1496
+ method: "GET",
1497
+ headers: getCLIHeaders(token)
1498
+ });
1499
+ cliDebug("fetchCurrentUserFromServer response", {
1500
+ status: response.status,
1501
+ ok: response.ok,
1502
+ durationMs: Date.now() - startedAt
1503
+ });
1504
+ if (response.status === 401 || response.status === 403) {
1505
+ throw await authLookupError(response, "Current user lookup");
1506
+ }
1507
+ if (!response.ok) {
1508
+ return null;
1509
+ }
1510
+ const user = await response.json();
1511
+ if (!user?.id) {
1512
+ return null;
1513
+ }
1514
+ cliDebug("fetchCurrentUserFromServer parsed", {
1515
+ userId: user.id,
1516
+ email: user.email,
1517
+ onboardingCompleted: !!user.preferences?.onboarding?.completedAt
1518
+ });
1519
+ return user;
1520
+ } catch (error) {
1521
+ if (isAuthLookupError(error)) {
1522
+ clearStoredAuthIfTokenMatches(token);
1523
+ throw error;
1524
+ }
1525
+ cliDebug("fetchCurrentUserFromServer failed", {
1526
+ message: errorMessage(error)
1527
+ });
1528
+ return null;
1529
+ }
1530
+ };
1531
+ var checkUserStatus = async (baseUrl, token) => {
1532
+ try {
1533
+ const apiBase = normalizeApiBase(baseUrl);
1534
+ cliDebug("checkUserStatus start", { apiBase });
1535
+ const currentUser = await fetchCurrentUserFromServer(apiBase, token);
1536
+ if (currentUser) {
1537
+ cliDebug("checkUserStatus resolved from /auth/me", {
1538
+ userId: currentUser.id,
1539
+ email: currentUser.email,
1540
+ onboardingCompleted: !!currentUser.preferences?.onboarding?.completedAt
1541
+ });
1542
+ return {
1543
+ exists: true,
1544
+ onboardingCompleted: !!currentUser.preferences?.onboarding?.completedAt,
1545
+ user: currentUser
1546
+ };
1547
+ }
1548
+ const email = extractEmailFromToken(token);
1549
+ if (!email) {
1550
+ cliDebug("checkUserStatus no email claim; assuming existing completed user");
1551
+ return { exists: true, onboardingCompleted: true };
1552
+ }
1553
+ const startedAt = Date.now();
1554
+ cliDebug("checkUserStatus /user/email request", {
1555
+ url: `${apiBase}/user/email`,
1556
+ email
1557
+ });
1558
+ const response = await fetch(`${apiBase}/user/email`, {
1559
+ method: "POST",
1560
+ headers: getCLIHeaders(token),
1561
+ body: JSON.stringify({ email })
1562
+ });
1563
+ cliDebug("checkUserStatus /user/email response", {
1564
+ status: response.status,
1565
+ ok: response.ok,
1566
+ durationMs: Date.now() - startedAt
1567
+ });
1568
+ if (response.status === 401 || response.status === 403) {
1569
+ throw await authLookupError(response, "User email lookup");
1570
+ }
1571
+ if (response.ok) {
1572
+ const user = await response.json();
1573
+ cliDebug("checkUserStatus /user/email parsed", {
1574
+ userId: user?.id,
1575
+ email: user?.email,
1576
+ onboardingCompleted: !!user.preferences?.onboarding?.completedAt
1577
+ });
1578
+ return {
1579
+ exists: true,
1580
+ onboardingCompleted: !!user.preferences?.onboarding?.completedAt,
1581
+ user
1582
+ };
1583
+ }
1584
+ if (response.status === 404) {
1585
+ cliDebug("checkUserStatus user missing");
1586
+ return { exists: false, onboardingCompleted: false };
1587
+ }
1588
+ cliDebug("checkUserStatus non-404 fallback", {
1589
+ status: response.status
1590
+ });
1591
+ return { exists: true, onboardingCompleted: true };
1592
+ } catch (error) {
1593
+ if (isAuthLookupError(error)) {
1594
+ clearStoredAuthIfTokenMatches(token);
1595
+ throw error;
1596
+ }
1597
+ cliDebug("checkUserStatus failed; assuming completed for compatibility", {
1598
+ message: errorMessage(error)
1599
+ });
1600
+ return { exists: true, onboardingCompleted: true };
1601
+ }
1602
+ };
1603
+ var completeOnboarding = async (baseUrl, token, data) => {
1604
+ try {
1605
+ const apiBase = normalizeApiBase(baseUrl);
1606
+ cliDebug("completeOnboarding start", {
1607
+ apiBase,
1608
+ name: data.name,
1609
+ role: data.role,
1610
+ teamSize: data.teamSize,
1611
+ goals: data.goals,
1612
+ cloudProvider: data.cloudProvider
1613
+ });
1614
+ const serverUser = await fetchCurrentUserFromServer(apiBase, token);
1615
+ const fallbackEmail = extractEmailFromToken(token);
1616
+ const email = serverUser?.email || fallbackEmail;
1617
+ if (!email) {
1618
+ throw new Error("Could not determine user email. Please login again.");
1619
+ }
1620
+ const userStatus = await checkUserStatus(apiBase, token);
1621
+ const knownUserId = userStatus.user?.id || serverUser?.id;
1622
+ cliDebug("completeOnboarding identity resolved", {
1623
+ email,
1624
+ serverUserId: serverUser?.id,
1625
+ knownUserId,
1626
+ statusExists: userStatus.exists,
1627
+ statusOnboardingCompleted: userStatus.onboardingCompleted
1628
+ });
1629
+ const onboarding = {
1630
+ role: data.role,
1631
+ teamSize: data.teamSize,
1632
+ primaryGoals: data.goals,
1633
+ cloudProvider: data.cloudProvider
1634
+ };
1635
+ const onboardData = await quickOnboardPlayground(
1636
+ apiBase,
1637
+ token,
1638
+ {
1639
+ id: knownUserId || "pending",
1640
+ email,
1641
+ fullName: data.name
1642
+ },
1643
+ {
1644
+ onboarding
1645
+ }
1646
+ );
1647
+ const userId = onboardData.user?.id || knownUserId;
1648
+ if (!userId) {
1649
+ throw new Error("Onboarding completed but no user ID returned");
1650
+ }
1651
+ const persistedOnboarding = onboardData.user?.preferences?.onboarding;
1652
+ const hasPersistedCompletion = !!persistedOnboarding?.completedAt || !!onboardData.onboarding_completed_at;
1653
+ cliDebug("completeOnboarding quick result", {
1654
+ userId,
1655
+ hasPersistedCompletion,
1656
+ hasPlaygroundProject: !!onboardData.playground_project?.id,
1657
+ setupJobs: onboardData.setup_jobs?.map((job) => job.operation)
1658
+ });
1659
+ if (!hasPersistedCompletion) {
1660
+ const preferences = {
1661
+ onboarding: {
1662
+ ...onboarding,
1663
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1664
+ }
1665
+ };
1666
+ cliDebug("completeOnboarding fallback PATCH request", {
1667
+ url: `${apiBase}/users/${userId}`
1668
+ });
1669
+ const response = await fetch(`${apiBase}/users/${userId}`, {
1670
+ method: "PATCH",
1671
+ headers: getCLIHeaders(token),
1672
+ body: JSON.stringify({ preferences })
1673
+ });
1674
+ cliDebug("completeOnboarding fallback PATCH response", {
1675
+ status: response.status,
1676
+ ok: response.ok
1677
+ });
1678
+ if (!response.ok) {
1679
+ const error = await response.json().catch(() => ({ message: "Failed to complete onboarding" }));
1680
+ throw new Error(error.message || "Failed to complete onboarding");
1681
+ }
1682
+ }
1683
+ if (!onboardData.playground_project?.id) {
1684
+ cliDebug("completeOnboarding quick response missing Playground; repairing");
1685
+ await ensurePlaygroundProject(apiBase, token, {
1686
+ id: userId,
1687
+ email,
1688
+ fullName: data.name
1689
+ });
1690
+ }
1691
+ cliDebug("completeOnboarding finished", {
1692
+ userId,
1693
+ email
1694
+ });
1695
+ } catch (error) {
1696
+ cliDebug("completeOnboarding failed", {
1697
+ message: error?.message
1698
+ });
1699
+ if (error?.message) {
1700
+ throw error;
1701
+ }
1702
+ throw new Error("Failed to complete onboarding");
1703
+ }
1704
+ };
1705
+ var getAuthToken = async (options = {}) => {
1706
+ if (options.accessKey) {
1707
+ return options.accessKey;
1708
+ }
1709
+ const minValidUntil = now() + TOKEN_EXPIRY_SKEW_MS;
1710
+ if (cachedToken && cachedToken.expiresAt > minValidUntil) {
1711
+ return cachedToken.token;
1712
+ }
1713
+ const disk = readStored();
1714
+ const refreshToken = getRefreshToken(disk);
1715
+ const accessToken = getAccessToken(disk);
1716
+ let refreshError;
1717
+ if (accessToken && disk.tokenExpiresAt && disk.tokenExpiresAt > minValidUntil) {
1718
+ cachedToken = { token: accessToken, expiresAt: disk.tokenExpiresAt };
1719
+ return accessToken;
1720
+ }
1721
+ if (refreshToken) {
1722
+ try {
1723
+ return await refreshWithSingleFlight(options);
1724
+ } catch (error) {
1725
+ refreshError = error;
1726
+ if (isRejectedRefreshTokenError(error)) {
1727
+ clearLocalAuth(disk);
1728
+ }
1729
+ }
1730
+ }
1731
+ const loginHint = "No authentication available. Run 'cloudeval login' to authenticate or use --access-key-stdin for automation.";
1732
+ if (refreshError) {
1733
+ throw new Error(`${loginHint} Stored refresh failed: ${errorMessage(refreshError)}`);
1734
+ }
1735
+ throw new Error(loginHint);
1736
+ };
1737
+ var getAuthHeader = async (options = {}) => {
1738
+ const token = await getAuthToken(options);
1739
+ return { Authorization: `Bearer ${token}` };
1740
+ };
1741
+ var createIdempotencyKey = () => randomUUID();
1742
+ var withIdempotencyHeader = (headers, idempotencyKey) => ({
1743
+ ...headers,
1744
+ "Idempotency-Key": idempotencyKey ?? createIdempotencyKey()
1745
+ });
1746
+ var DEFAULT_PROJECT_TYPE = "sync";
1747
+ var RESPONSE_OUTPUT_NODES = /* @__PURE__ */ new Set([
1748
+ "generate_response",
1749
+ "handle_social_interaction",
1750
+ "response_compose"
1751
+ ]);
1752
+ var isLocalHostname2 = (hostname) => {
1753
+ const lower = hostname.toLowerCase();
1754
+ return lower === "localhost" || lower === "127.0.0.1" || lower === "::1" || lower === "[::1]";
1755
+ };
1756
+ var assertSecureBaseUrl2 = (rawBaseUrl) => {
1757
+ const parsed = new URL(rawBaseUrl);
1758
+ if (parsed.protocol === "https:") {
1759
+ return;
1760
+ }
1761
+ if (parsed.protocol === "http:" && isLocalHostname2(parsed.hostname)) {
1762
+ return;
1763
+ }
1764
+ throw new Error(
1765
+ `Refusing insecure base URL (${rawBaseUrl}). Use HTTPS for non-localhost endpoints.`
1766
+ );
1767
+ };
1768
+ var isObject2 = (value) => typeof value === "object" && value !== null;
1769
+ var isValidChunkStatus = (value) => {
1770
+ return typeof value === "string" && (value === "streaming" || value === "completed" || value === "aborted" || value === "error" || value === "pending");
1771
+ };
1772
+ var stringOrUndefined = (value) => typeof value === "string" ? value : void 0;
1773
+ var numberOrUndefined = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
1774
+ var booleanOrUndefined = (value) => typeof value === "boolean" ? value : void 0;
1775
+ var isResponseOutputChunk = (chunk) => chunk.type === "responding" && (!chunk.node || RESPONSE_OUTPUT_NODES.has(chunk.node));
1776
+ var isResponseCompletionChunk = (chunk) => isResponseOutputChunk(chunk) && chunk.status === "completed";
1777
+ var isTerminalEndChunk = (chunk) => (chunk.type === "thinking" || chunk.type === "responding") && chunk.status === "completed" && chunk.node === "end";
1778
+ var normalizeHitlOption = (raw, index) => {
1779
+ if (!isObject2(raw)) {
1780
+ const label = String(raw ?? `Option ${index + 1}`);
1781
+ return { id: label, label };
1782
+ }
1783
+ const id = stringOrUndefined(raw.id) ?? stringOrUndefined(raw.value) ?? stringOrUndefined(raw.label) ?? `option_${index}`;
1784
+ return {
1785
+ id,
1786
+ label: stringOrUndefined(raw.label) ?? id,
1787
+ description: stringOrUndefined(raw.description),
1788
+ recommended: booleanOrUndefined(raw.recommended)
1789
+ };
1790
+ };
1791
+ var normalizeHitlQuestion = (raw, index) => {
1792
+ if (!isObject2(raw)) {
1793
+ return {
1794
+ id: `question_${index}`,
1795
+ text: String(raw ?? "Action required")
1796
+ };
1797
+ }
1798
+ const id = stringOrUndefined(raw.id) ?? stringOrUndefined(raw.question_id) ?? `question_${index}`;
1799
+ const text = stringOrUndefined(raw.text) ?? stringOrUndefined(raw.label) ?? stringOrUndefined(raw.message) ?? "Action required";
1800
+ const options = Array.isArray(raw.options) ? raw.options.map(
1801
+ (option, optionIndex) => normalizeHitlOption(option, optionIndex)
1802
+ ) : void 0;
1803
+ return {
1804
+ id,
1805
+ text,
1806
+ label: stringOrUndefined(raw.label),
1807
+ kind: stringOrUndefined(raw.kind),
1808
+ intent: stringOrUndefined(raw.intent),
1809
+ tool_label: stringOrUndefined(raw.tool_label),
1810
+ action: stringOrUndefined(raw.action),
1811
+ options,
1812
+ recommended_option_id: stringOrUndefined(raw.recommended_option_id),
1813
+ mode_switch_source: stringOrUndefined(raw.mode_switch_source),
1814
+ mode_switch_target: stringOrUndefined(raw.mode_switch_target),
1815
+ resume_behavior: stringOrUndefined(raw.resume_behavior),
1816
+ selectionMode: stringOrUndefined(raw.selectionMode),
1817
+ minSelections: numberOrUndefined(raw.minSelections),
1818
+ maxSelections: numberOrUndefined(raw.maxSelections)
1819
+ };
1820
+ };
1821
+ var normalizeChunk = (raw, receivedAt) => {
1822
+ if (!isObject2(raw) || typeof raw.type !== "string") {
1823
+ return null;
1824
+ }
1825
+ const base = { receivedAt };
1826
+ const data = isObject2(raw.data) ? raw.data : void 0;
1827
+ switch (raw.type) {
1828
+ case "metadata": {
1829
+ const chunk = {
1830
+ type: "metadata",
1831
+ trace_id: typeof raw.trace_id === "string" ? raw.trace_id : void 0,
1832
+ thread_id: typeof raw.thread_id === "string" ? raw.thread_id : void 0,
1833
+ ...base
1834
+ };
1835
+ return chunk;
1836
+ }
1837
+ case "thinking": {
1838
+ const chunk = {
1839
+ type: "thinking",
1840
+ node: typeof raw.node === "string" ? raw.node : void 0,
1841
+ status: isValidChunkStatus(raw.status) ? raw.status : void 0,
1842
+ description: typeof raw.description === "string" ? raw.description : void 0,
1843
+ message: typeof raw.message === "string" ? raw.message : void 0,
1844
+ content: typeof raw.content === "string" ? raw.content : void 0,
1845
+ ...base
1846
+ };
1847
+ return chunk;
1848
+ }
1849
+ case "responding": {
1850
+ const chunk = {
1851
+ type: "responding",
1852
+ node: typeof raw.node === "string" ? raw.node : void 0,
1853
+ status: isValidChunkStatus(raw.status) ? raw.status : void 0,
1854
+ description: typeof raw.description === "string" ? raw.description : void 0,
1855
+ message: typeof raw.message === "string" ? raw.message : void 0,
1856
+ content: typeof raw.content === "string" ? raw.content : void 0,
1857
+ source: stringOrUndefined(raw.source),
1858
+ ...base
1859
+ };
1860
+ return chunk;
1861
+ }
1862
+ case "error": {
1863
+ const chunk = {
1864
+ type: "error",
1865
+ node: typeof raw.node === "string" ? raw.node : void 0,
1866
+ status: isValidChunkStatus(raw.status) ? raw.status : void 0,
1867
+ description: typeof raw.description === "string" ? raw.description : void 0,
1868
+ message: typeof raw.message === "string" ? raw.message : void 0,
1869
+ content: typeof raw.content === "string" ? raw.content : void 0,
1870
+ stacktrace: typeof raw.stacktrace === "string" ? raw.stacktrace : void 0,
1871
+ ...base
1872
+ };
1873
+ return chunk;
1874
+ }
1875
+ case "hitl":
1876
+ case "hitl_request": {
1877
+ const rawQuestions = Array.isArray(raw.questions) ? raw.questions : Array.isArray(data?.questions) ? data.questions : [];
1878
+ const chunk = {
1879
+ type: "hitl_request",
1880
+ questions: rawQuestions.map(
1881
+ (question, index) => normalizeHitlQuestion(question, index)
1882
+ ),
1883
+ checkpoint_id: stringOrUndefined(raw.checkpoint_id) ?? stringOrUndefined(data?.checkpoint_id),
1884
+ pending_intent_id: stringOrUndefined(raw.pending_intent_id) ?? stringOrUndefined(data?.pending_intent_id),
1885
+ run_id: stringOrUndefined(raw.run_id) ?? stringOrUndefined(data?.run_id),
1886
+ langsmith_trace_id: stringOrUndefined(raw.langsmith_trace_id) ?? stringOrUndefined(data?.langsmith_trace_id),
1887
+ ...base
1888
+ };
1889
+ return chunk;
1890
+ }
1891
+ case "hitl_resume": {
1892
+ const chunk = {
1893
+ type: "hitl_resume",
1894
+ status: isValidChunkStatus(raw.status) ? raw.status : void 0,
1895
+ message: typeof raw.message === "string" ? raw.message : void 0,
1896
+ pending_intent_id: stringOrUndefined(raw.pending_intent_id) ?? stringOrUndefined(data?.pending_intent_id),
1897
+ ...base
1898
+ };
1899
+ return chunk;
1900
+ }
1901
+ default:
1902
+ return null;
1903
+ }
1904
+ };
1905
+ var buildPayload = (options) => {
1906
+ const user = options.project?.user_id && (!options.user.id || options.user.id === "cli-user") ? { ...options.user, id: options.project.user_id } : options.user;
1907
+ const project = options.project ?? {
1908
+ id: "cli-project",
1909
+ name: "CLI Session",
1910
+ user_id: user.id,
1911
+ cloud_provider: "azure",
1912
+ type: DEFAULT_PROJECT_TYPE
1913
+ };
1914
+ const context = options.context ?? [];
1915
+ const settings = options.settings;
1916
+ const message = options.message;
1917
+ const agentProfileId = options.agentProfileId?.trim();
1918
+ const payload = {
1919
+ thread_id: options.threadId,
1920
+ input: {
1921
+ messages: [{ role: "user", content: message }],
1922
+ user,
1923
+ project,
1924
+ settings,
1925
+ ...agentProfileId ? { agent_profile_id: agentProfileId } : {},
1926
+ context
1927
+ },
1928
+ user,
1929
+ message,
1930
+ project,
1931
+ settings,
1932
+ ...agentProfileId ? { agent_profile_id: agentProfileId } : {},
1933
+ context,
1934
+ group_size: 1,
1935
+ streaming_mode: options.streamingMode ?? "USER"
1936
+ };
1937
+ if (options.hitlResume?.checkpointId && options.hitlResume.responses.length > 0) {
1938
+ payload.hitl_resume = true;
1939
+ payload.hitl_checkpoint_id = options.hitlResume.checkpointId;
1940
+ payload.hitl_responses = options.hitlResume.responses;
1941
+ if (options.hitlResume.runId) {
1942
+ payload.run_id = options.hitlResume.runId;
1943
+ }
1944
+ if (options.hitlResume.langsmithTraceId) {
1945
+ payload.langsmith_trace_id = options.hitlResume.langsmithTraceId;
1946
+ }
1947
+ }
1948
+ return payload;
1949
+ };
1950
+ var compactErrorBody = (body) => {
1951
+ const trimmed = body.trim();
1952
+ if (!trimmed) {
1953
+ return void 0;
1954
+ }
1955
+ try {
1956
+ const parsed = JSON.parse(trimmed);
1957
+ return redactSensitiveText(JSON.stringify(parsed));
1958
+ } catch {
1959
+ const redacted = redactSensitiveText(trimmed);
1960
+ return redacted.length > 1e3 ? `${redacted.slice(0, 1e3)}...` : redacted;
1961
+ }
1962
+ };
1963
+ async function* streamChat(options) {
1964
+ assertSecureBaseUrl2(options.baseUrl);
1965
+ const payload = buildPayload(options);
1966
+ const apiBase = normalizeApiBase(options.baseUrl);
1967
+ const url = `${apiBase}/chat/stream`;
1968
+ const streamIdleTimeoutMs = typeof options.streamIdleTimeoutMs === "number" && Number.isFinite(options.streamIdleTimeoutMs) && options.streamIdleTimeoutMs > 0 ? options.streamIdleTimeoutMs : void 0;
1969
+ const headers = withIdempotencyHeader({
1970
+ "Content-Type": "application/json",
1971
+ Accept: "text/event-stream",
1972
+ "X-Client-Type": "cloudeval-cli",
1973
+ "X-Client-Version": "0.1.0"
1974
+ });
1975
+ if (options.authToken) {
1976
+ headers.Authorization = `Bearer ${options.authToken}`;
1977
+ }
1978
+ let connectTimedOut = false;
1979
+ let connectTimeout;
1980
+ let removeExternalAbort;
1981
+ let requestSignal = options.signal;
1982
+ const connectController = streamIdleTimeoutMs ? new AbortController() : void 0;
1983
+ if (connectController) {
1984
+ requestSignal = connectController.signal;
1985
+ if (options.signal) {
1986
+ const abortFromExternal = () => connectController.abort(options.signal?.reason);
1987
+ if (options.signal.aborted) {
1988
+ abortFromExternal();
1989
+ } else {
1990
+ options.signal.addEventListener("abort", abortFromExternal, { once: true });
1991
+ removeExternalAbort = () => options.signal?.removeEventListener("abort", abortFromExternal);
1992
+ }
1993
+ }
1994
+ connectTimeout = setTimeout(() => {
1995
+ connectTimedOut = true;
1996
+ connectController.abort();
1997
+ }, streamIdleTimeoutMs);
1998
+ }
1999
+ let response;
2000
+ try {
2001
+ response = await fetch(url, {
2002
+ method: "POST",
2003
+ headers,
2004
+ body: JSON.stringify(payload),
2005
+ signal: requestSignal
2006
+ });
2007
+ } catch (error) {
2008
+ if (connectTimedOut && streamIdleTimeoutMs) {
2009
+ throw new Error(
2010
+ `No chat stream response received within ${streamIdleTimeoutMs}ms. Please retry or check backend availability.`
2011
+ );
2012
+ }
2013
+ throw error;
2014
+ } finally {
2015
+ if (connectTimeout) {
2016
+ clearTimeout(connectTimeout);
2017
+ }
2018
+ removeExternalAbort?.();
2019
+ }
2020
+ if (!response.ok) {
2021
+ const body = compactErrorBody(await response.text().catch(() => ""));
2022
+ throw new Error(
2023
+ `Stream request failed with status ${response.status} ${response.statusText}${body ? `: ${body}` : ""}`
2024
+ );
2025
+ }
2026
+ if (!response.body) {
2027
+ throw new Error("Streaming response body missing");
2028
+ }
2029
+ const reader = response.body.getReader();
2030
+ const decoder = new TextDecoder("utf-8");
2031
+ let buffer = "";
2032
+ let sseDataLines = [];
2033
+ let doneSeen = false;
2034
+ let responseCompleteDeadline;
2035
+ let streamActivityAt = Date.now();
2036
+ const responseCompletionGraceMs = options.responseCompletionGraceMs ?? 5e3;
2037
+ const parsePayload = (rawPayload) => {
2038
+ const trimmed = rawPayload.trim();
2039
+ if (!trimmed) {
2040
+ return;
2041
+ }
2042
+ if (trimmed === "[DONE]") {
2043
+ doneSeen = true;
2044
+ return;
2045
+ }
2046
+ try {
2047
+ const parsed = JSON.parse(trimmed);
2048
+ const chunk = normalizeChunk(parsed, Date.now());
2049
+ if (chunk && options.debug) {
2050
+ console.error("[stream][chunk]", chunk);
2051
+ }
2052
+ if (chunk) {
2053
+ return chunk;
2054
+ }
2055
+ } catch (error) {
2056
+ if (options.debug) {
2057
+ console.error("[stream][parse-error]", error, rawPayload);
2058
+ }
2059
+ }
2060
+ };
2061
+ const flushSseEvent = () => {
2062
+ if (sseDataLines.length === 0) {
2063
+ return;
2064
+ }
2065
+ const payload2 = sseDataLines.join("\n");
2066
+ sseDataLines = [];
2067
+ return parsePayload(payload2);
2068
+ };
2069
+ const parseLine = (line) => {
2070
+ const normalizedLine = line.replace(/\r$/, "");
2071
+ if (!normalizedLine) {
2072
+ return flushSseEvent();
2073
+ }
2074
+ if (normalizedLine.startsWith(":")) {
2075
+ return;
2076
+ }
2077
+ if (normalizedLine.startsWith("data:")) {
2078
+ sseDataLines.push(normalizedLine.slice(5).trimStart());
2079
+ return;
2080
+ }
2081
+ if (normalizedLine.startsWith("event:") || normalizedLine.startsWith("id:") || normalizedLine.startsWith("retry:")) {
2082
+ return;
2083
+ }
2084
+ if (sseDataLines.length > 0) {
2085
+ sseDataLines.push(normalizedLine);
2086
+ return;
2087
+ }
2088
+ return parsePayload(normalizedLine);
2089
+ };
2090
+ const readWithOptionalDeadline = async () => {
2091
+ if (!responseCompleteDeadline && !streamIdleTimeoutMs) {
2092
+ return { type: "read", result: await reader.read() };
2093
+ }
2094
+ const deadline = responseCompleteDeadline ?? streamActivityAt + streamIdleTimeoutMs;
2095
+ const remainingMs = deadline - Date.now();
2096
+ if (remainingMs <= 0) {
2097
+ return responseCompleteDeadline ? { type: "deadline" } : { type: "idle-timeout" };
2098
+ }
2099
+ return Promise.race([
2100
+ reader.read().then((result) => ({ type: "read", result })),
2101
+ new Promise(
2102
+ (resolve) => setTimeout(
2103
+ () => resolve(
2104
+ responseCompleteDeadline ? { type: "deadline" } : { type: "idle-timeout" }
2105
+ ),
2106
+ remainingMs
2107
+ )
2108
+ )
2109
+ ]);
2110
+ };
2111
+ const markResponseComplete = (chunk) => {
2112
+ if (!options.completeAfterResponse) {
2113
+ return;
2114
+ }
2115
+ if (isTerminalEndChunk(chunk)) {
2116
+ responseCompleteDeadline ??= Date.now() + responseCompletionGraceMs;
2117
+ return;
2118
+ }
2119
+ if (!isResponseOutputChunk(chunk)) {
2120
+ return;
2121
+ }
2122
+ if (isResponseCompletionChunk(chunk)) {
2123
+ responseCompleteDeadline ??= Date.now() + responseCompletionGraceMs;
2124
+ return;
2125
+ }
2126
+ if (chunk.content) {
2127
+ responseCompleteDeadline = Date.now() + responseCompletionGraceMs;
2128
+ }
2129
+ };
2130
+ try {
2131
+ while (true) {
2132
+ const read = await readWithOptionalDeadline();
2133
+ if (read.type === "deadline") {
2134
+ return;
2135
+ }
2136
+ if (read.type === "idle-timeout") {
2137
+ throw new Error(
2138
+ `No chat stream data received within ${streamIdleTimeoutMs}ms. Please retry or check backend availability.`
2139
+ );
2140
+ }
2141
+ const { value, done } = read.result;
2142
+ if (done) break;
2143
+ if (value?.length) {
2144
+ streamActivityAt = Date.now();
2145
+ }
2146
+ buffer += decoder.decode(value, { stream: true });
2147
+ const lines = buffer.split("\n");
2148
+ buffer = lines.pop() ?? "";
2149
+ for (const line of lines) {
2150
+ const chunk = parseLine(line);
2151
+ if (chunk) {
2152
+ yield chunk;
2153
+ markResponseComplete(chunk);
2154
+ }
2155
+ if (doneSeen) {
2156
+ return;
2157
+ }
2158
+ }
2159
+ }
2160
+ if (buffer.trim()) {
2161
+ const chunk = parseLine(buffer);
2162
+ if (chunk) {
2163
+ yield chunk;
2164
+ markResponseComplete(chunk);
2165
+ }
2166
+ if (doneSeen) {
2167
+ return;
2168
+ }
2169
+ }
2170
+ const finalChunk = flushSseEvent();
2171
+ if (finalChunk) {
2172
+ yield finalChunk;
2173
+ markResponseComplete(finalChunk);
2174
+ }
2175
+ } finally {
2176
+ await reader.cancel().catch(() => void 0);
2177
+ try {
2178
+ reader.releaseLock();
2179
+ } catch {
2180
+ }
2181
+ }
2182
+ }
2183
+ var STREAMING_NODES = /* @__PURE__ */ new Set([
2184
+ "generate_response",
2185
+ "handle_social_interaction",
2186
+ "response_compose"
2187
+ ]);
2188
+ var FOLLOW_UP_NODE = "generate_follow_up";
2189
+ var ERROR_FALLBACK_SOURCE_PREFIX = "error_fallback:";
2190
+ var TERMINAL_STEP_STATUSES = /* @__PURE__ */ new Set([
2191
+ "completed",
2192
+ "error",
2193
+ "aborted",
2194
+ "cancelled"
2195
+ ]);
2196
+ var cloneMessage = (message) => ({
2197
+ ...message,
2198
+ thinkingSteps: message.thinkingSteps ? [...message.thinkingSteps.map((s) => ({ ...s }))] : [],
2199
+ followUpQuestions: message.followUpQuestions ? [...message.followUpQuestions] : void 0
2200
+ });
2201
+ var finalizeOpenSteps = (steps, status, timestamp) => {
2202
+ if (!steps) {
2203
+ return steps;
2204
+ }
2205
+ return steps.map((step) => {
2206
+ if (TERMINAL_STEP_STATUSES.has(step.status ?? "pending")) {
2207
+ return step;
2208
+ }
2209
+ const startedAt = step.startedAt ?? step.timestamp;
2210
+ return {
2211
+ ...step,
2212
+ status,
2213
+ timestamp,
2214
+ updatedAt: timestamp,
2215
+ completedAt: timestamp,
2216
+ durationMs: Math.max(0, timestamp - startedAt)
2217
+ };
2218
+ });
2219
+ };
2220
+ var ensureAssistantMessage = (state, timestamp) => {
2221
+ const existing = state.activeMessageId && state.messages.find(
2222
+ (m) => m.id === state.activeMessageId && m.role === "assistant"
2223
+ ) || state.messages.find((m) => m.pending && m.role === "assistant");
2224
+ if (existing) {
2225
+ return [{ ...state, activeMessageId: existing.id }, cloneMessage(existing)];
2226
+ }
2227
+ const closedMessages = state.messages.map(
2228
+ (m) => m.role === "assistant" && m.pending ? { ...m, pending: false, updatedAt: timestamp } : m
2229
+ );
2230
+ const message = {
2231
+ id: randomUUID2(),
2232
+ role: "assistant",
2233
+ content: "",
2234
+ pending: true,
2235
+ thinkingSteps: [],
2236
+ createdAt: timestamp,
2237
+ updatedAt: timestamp
2238
+ };
2239
+ return [
2240
+ {
2241
+ ...state,
2242
+ activeMessageId: message.id,
2243
+ messages: [...closedMessages, message]
2244
+ },
2245
+ message
2246
+ ];
2247
+ };
2248
+ var mergeThinkingStep = (steps, chunk) => {
2249
+ const node = chunk.node ?? "unknown";
2250
+ const mergedSteps = steps ? [...steps] : [];
2251
+ const chunkStatus = chunk.status ?? "streaming";
2252
+ const existingIdx = [...mergedSteps].reverse().findIndex(
2253
+ (s) => s.node === node && !TERMINAL_STEP_STATUSES.has(s.status ?? "pending")
2254
+ );
2255
+ const existingStepIdx = existingIdx >= 0 ? mergedSteps.length - 1 - existingIdx : -1;
2256
+ if (existingStepIdx >= 0) {
2257
+ const existing = mergedSteps[existingStepIdx];
2258
+ const startedAt = existing.startedAt ?? existing.timestamp ?? chunk.receivedAt;
2259
+ const isTerminal = TERMINAL_STEP_STATUSES.has(chunkStatus);
2260
+ mergedSteps[existingStepIdx] = {
2261
+ ...existing,
2262
+ content: (existing.content ?? "") + (chunk.content ?? ""),
2263
+ description: chunk.description ?? existing.description,
2264
+ message: chunk.message ?? existing.message,
2265
+ status: chunkStatus,
2266
+ timestamp: chunk.receivedAt,
2267
+ startedAt,
2268
+ updatedAt: chunk.receivedAt,
2269
+ completedAt: isTerminal ? chunk.receivedAt : existing.completedAt,
2270
+ durationMs: Math.max(0, chunk.receivedAt - startedAt)
2271
+ };
2272
+ } else {
2273
+ const isTerminal = TERMINAL_STEP_STATUSES.has(chunkStatus);
2274
+ mergedSteps.push({
2275
+ node,
2276
+ type: chunk.type === "responding" ? "responding" : "thinking",
2277
+ content: chunk.content,
2278
+ description: chunk.description,
2279
+ message: chunk.message,
2280
+ status: chunkStatus,
2281
+ timestamp: chunk.receivedAt,
2282
+ startedAt: chunk.receivedAt,
2283
+ updatedAt: chunk.receivedAt,
2284
+ completedAt: isTerminal ? chunk.receivedAt : void 0,
2285
+ durationMs: 0
2286
+ });
2287
+ }
2288
+ return mergedSteps;
2289
+ };
2290
+ var mergeHitlStep = (steps, chunk) => {
2291
+ const node = "hitl_middleware";
2292
+ const existingIdx = steps?.findIndex((s) => s.node === node) ?? -1;
2293
+ const mergedSteps = steps ? [...steps] : [];
2294
+ const firstQuestion = chunk.questions[0];
2295
+ const step = {
2296
+ node,
2297
+ type: "hitl",
2298
+ description: "Waiting for your input",
2299
+ message: firstQuestion?.text,
2300
+ content: firstQuestion?.text,
2301
+ status: "pending",
2302
+ timestamp: chunk.receivedAt,
2303
+ startedAt: chunk.receivedAt,
2304
+ updatedAt: chunk.receivedAt,
2305
+ durationMs: 0,
2306
+ hitlQuestions: chunk.questions
2307
+ };
2308
+ if (existingIdx >= 0) {
2309
+ mergedSteps[existingIdx] = {
2310
+ ...mergedSteps[existingIdx],
2311
+ ...step
2312
+ };
2313
+ } else {
2314
+ mergedSteps.push(step);
2315
+ }
2316
+ return mergedSteps;
2317
+ };
2318
+ var parseFollowUps = (scratch) => (scratch ?? "").split(";").map((q) => q.trim()).filter(Boolean);
2319
+ var isErrorFallbackRespondingChunk = (chunk) => typeof chunk.source === "string" && chunk.source.startsWith(ERROR_FALLBACK_SOURCE_PREFIX);
2320
+ var getFallbackErrorMessage = (chunk) => chunk.content || chunk.description || chunk.message || "Unknown error";
2321
+ var initialChatState = {
2322
+ status: "idle",
2323
+ messages: []
2324
+ };
2325
+ var completeActiveAssistantMessage = (state, timestamp = Date.now()) => {
2326
+ const activeMessage = state.activeMessageId && state.messages.find(
2327
+ (message) => message.id === state.activeMessageId && message.role === "assistant"
2328
+ ) || [...state.messages].reverse().find(
2329
+ (message) => message.role === "assistant" && message.pending
2330
+ ) || [...state.messages].reverse().find(
2331
+ (message) => message.role === "assistant"
2332
+ );
2333
+ if (!activeMessage) {
2334
+ return {
2335
+ ...state,
2336
+ status: state.status === "error" || state.status === "hitl_waiting" ? state.status : "complete"
2337
+ };
2338
+ }
2339
+ return {
2340
+ ...state,
2341
+ status: state.status === "error" || state.status === "hitl_waiting" ? state.status : "complete",
2342
+ messages: state.messages.map(
2343
+ (message) => message.id === activeMessage.id ? {
2344
+ ...message,
2345
+ pending: false,
2346
+ updatedAt: timestamp,
2347
+ thinkingSteps: finalizeOpenSteps(
2348
+ message.thinkingSteps,
2349
+ "completed",
2350
+ timestamp
2351
+ )
2352
+ } : message
2353
+ )
2354
+ };
2355
+ };
2356
+ var reduceChunk = (state, chunk) => {
2357
+ const next = {
2358
+ ...state,
2359
+ lastChunk: chunk
2360
+ };
2361
+ if (chunk.type === "metadata") {
2362
+ return {
2363
+ ...next,
2364
+ threadId: chunk.thread_id ?? next.threadId,
2365
+ traceId: chunk.trace_id ?? next.traceId
2366
+ };
2367
+ }
2368
+ if (chunk.type === "hitl_request") {
2369
+ const [stateWithMessage, streamingMessage] = ensureAssistantMessage(
2370
+ next,
2371
+ chunk.receivedAt
2372
+ );
2373
+ const updatedMessage = cloneMessage(streamingMessage);
2374
+ updatedMessage.pending = true;
2375
+ updatedMessage.thinkingSteps = mergeHitlStep(
2376
+ updatedMessage.thinkingSteps,
2377
+ chunk
2378
+ );
2379
+ updatedMessage.updatedAt = chunk.receivedAt;
2380
+ const messages = stateWithMessage.messages.map(
2381
+ (m) => m.id === updatedMessage.id ? updatedMessage : m
2382
+ );
2383
+ return {
2384
+ ...stateWithMessage,
2385
+ status: "hitl_waiting",
2386
+ messages,
2387
+ activeMessageId: updatedMessage.id,
2388
+ followUpScratch: void 0,
2389
+ hitl: {
2390
+ waiting: true,
2391
+ questions: chunk.questions,
2392
+ checkpointId: chunk.checkpoint_id,
2393
+ pendingIntentId: chunk.pending_intent_id,
2394
+ runId: chunk.run_id,
2395
+ langsmithTraceId: chunk.langsmith_trace_id,
2396
+ messageId: updatedMessage.id
2397
+ }
2398
+ };
2399
+ }
2400
+ if (chunk.type === "hitl_resume") {
2401
+ return {
2402
+ ...next,
2403
+ status: "thinking",
2404
+ hitl: void 0,
2405
+ error: void 0
2406
+ };
2407
+ }
2408
+ if (chunk.type === "error") {
2409
+ const status = chunk.status === "aborted" ? "canceled" : "error";
2410
+ const error = chunk.description || chunk.content || chunk.message || "Unknown error";
2411
+ const messages = state.messages.map(
2412
+ (m) => m.id === state.activeMessageId ? {
2413
+ ...m,
2414
+ pending: false,
2415
+ error,
2416
+ updatedAt: chunk.receivedAt,
2417
+ thinkingSteps: finalizeOpenSteps(
2418
+ m.thinkingSteps,
2419
+ status === "canceled" ? "aborted" : "error",
2420
+ chunk.receivedAt
2421
+ )
2422
+ } : m
2423
+ );
2424
+ return {
2425
+ ...next,
2426
+ status,
2427
+ error,
2428
+ messages,
2429
+ hitl: void 0
2430
+ };
2431
+ }
2432
+ if (chunk.type === "thinking" || chunk.type === "responding") {
2433
+ const [stateWithMessage, streamingMessage] = ensureAssistantMessage(
2434
+ next,
2435
+ chunk.receivedAt
2436
+ );
2437
+ const updatedMessage = cloneMessage(streamingMessage);
2438
+ updatedMessage.thinkingSteps = mergeThinkingStep(
2439
+ updatedMessage.thinkingSteps,
2440
+ chunk
2441
+ );
2442
+ updatedMessage.updatedAt = chunk.receivedAt;
2443
+ if (chunk.type === "responding" && isErrorFallbackRespondingChunk(chunk)) {
2444
+ const error = getFallbackErrorMessage(chunk);
2445
+ updatedMessage.content = `${updatedMessage.content ?? ""}${chunk.content ?? ""}`;
2446
+ updatedMessage.pending = false;
2447
+ updatedMessage.updatedAt = chunk.receivedAt;
2448
+ updatedMessage.thinkingSteps = finalizeOpenSteps(
2449
+ updatedMessage.thinkingSteps,
2450
+ "error",
2451
+ chunk.receivedAt
2452
+ );
2453
+ const messages2 = stateWithMessage.messages.map(
2454
+ (m) => m.id === updatedMessage.id ? updatedMessage : m
2455
+ );
2456
+ return {
2457
+ ...stateWithMessage,
2458
+ status: "error",
2459
+ error,
2460
+ followUpScratch: void 0,
2461
+ messages: messages2,
2462
+ activeMessageId: updatedMessage.id,
2463
+ hitl: void 0
2464
+ };
2465
+ }
2466
+ let followUpScratch = stateWithMessage.followUpScratch;
2467
+ let status = stateWithMessage.status;
2468
+ const keepHitlWaiting = Boolean(stateWithMessage.hitl?.waiting);
2469
+ if (chunk.type === "responding" && STREAMING_NODES.has(chunk.node ?? "")) {
2470
+ updatedMessage.content = `${updatedMessage.content ?? ""}${chunk.content ?? ""}`;
2471
+ updatedMessage.pending = chunk.status !== "completed";
2472
+ if (chunk.status === "completed") {
2473
+ updatedMessage.thinkingSteps = finalizeOpenSteps(
2474
+ updatedMessage.thinkingSteps,
2475
+ "completed",
2476
+ chunk.receivedAt
2477
+ );
2478
+ }
2479
+ status = chunk.status === "completed" ? "complete" : "streaming";
2480
+ } else if (chunk.type === "responding" && (chunk.node ?? "") === FOLLOW_UP_NODE) {
2481
+ followUpScratch = `${followUpScratch ?? ""}${chunk.content ?? ""}`;
2482
+ if (chunk.status === "completed") {
2483
+ updatedMessage.followUpQuestions = parseFollowUps(followUpScratch);
2484
+ followUpScratch = void 0;
2485
+ }
2486
+ status = "tool_running";
2487
+ } else {
2488
+ status = chunk.status === "completed" && chunk.node === "end" ? "complete" : stateWithMessage.status === "idle" || stateWithMessage.status === "connecting" ? "thinking" : stateWithMessage.status;
2489
+ updatedMessage.pending = chunk.status !== "completed" || updatedMessage.pending;
2490
+ if (chunk.status === "completed" && chunk.node === "end") {
2491
+ updatedMessage.thinkingSteps = finalizeOpenSteps(
2492
+ updatedMessage.thinkingSteps,
2493
+ "completed",
2494
+ chunk.receivedAt
2495
+ );
2496
+ }
2497
+ }
2498
+ if (keepHitlWaiting) {
2499
+ status = "hitl_waiting";
2500
+ updatedMessage.pending = true;
2501
+ }
2502
+ const messages = stateWithMessage.messages.map(
2503
+ (m) => m.id === updatedMessage.id ? updatedMessage : m
2504
+ );
2505
+ return {
2506
+ ...stateWithMessage,
2507
+ status,
2508
+ followUpScratch,
2509
+ messages,
2510
+ activeMessageId: updatedMessage.id,
2511
+ hitl: keepHitlWaiting ? stateWithMessage.hitl : void 0
2512
+ };
2513
+ }
2514
+ return next;
2515
+ };
2516
+ var appendQuery = (url, values) => {
2517
+ for (const [key, value] of Object.entries(values)) {
2518
+ if (value) {
2519
+ url.searchParams.set(key, value);
2520
+ }
2521
+ }
2522
+ return url;
2523
+ };
2524
+ var compactErrorBody2 = async (response) => {
2525
+ const body = await response.text().catch(() => "");
2526
+ const trimmed = body.trim();
2527
+ if (!trimmed) {
2528
+ return void 0;
2529
+ }
2530
+ const redacted = redactSensitiveText(trimmed);
2531
+ return redacted.length > 1e3 ? `${redacted.slice(0, 1e3)}...` : redacted;
2532
+ };
2533
+ var isObject22 = (value) => typeof value === "object" && value !== null;
2534
+ var stringOr2 = (value, fallback) => typeof value === "string" && value.trim() ? value : fallback;
2535
+ var numberOr = (value, fallback = 0) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
2536
+ var arrayOrEmpty = (value) => Array.isArray(value) ? value : [];
2537
+ var reportTypeToKind = (value) => value === "architecture" || value === "waf" ? "waf" : "cost";
2538
+ var backendReportType = (kind) => {
2539
+ if (!kind || kind === "all") return void 0;
2540
+ return kind === "waf" ? "architecture" : kind;
2541
+ };
2542
+ var requireProjectId = (projectId) => {
2543
+ if (!projectId) {
2544
+ throw new Error("Project ID is required for report requests.");
2545
+ }
2546
+ return projectId;
2547
+ };
2548
+ var requireUserId = (userId) => {
2549
+ if (!userId) {
2550
+ throw new Error("Authenticated user ID is required for report requests.");
2551
+ }
2552
+ return userId;
2553
+ };
2554
+ var normalizeCostParsed = (report, metrics = {}) => {
2555
+ const parsed = isObject22(report.parsed) ? report.parsed : void 0;
2556
+ if (parsed && isObject22(parsed.totalSpend) && isObject22(parsed.estimatedSavings)) {
2557
+ return parsed;
2558
+ }
2559
+ const processed = isObject22(report.processed) ? report.processed : {};
2560
+ const dashboard = isObject22(report.dashboard) ? report.dashboard : {};
2561
+ const source = { ...metrics, ...dashboard, ...processed };
2562
+ const currency = stringOr2(source.currency, "USD");
2563
+ const monthly = numberOr(
2564
+ source.total_monthly_cost ?? source.monthly_cost ?? source.monthly,
2565
+ 0
2566
+ );
2567
+ const savings = numberOr(
2568
+ source.total_monthly_savings ?? (isObject22(source.opportunity_summary) ? source.opportunity_summary.total_monthly_savings : void 0) ?? source.monthly_savings,
2569
+ 0
2570
+ );
2571
+ return {
2572
+ totalSpend: {
2573
+ amount: monthly,
2574
+ currency,
2575
+ changePercent: numberOr(source.change_percent, 0)
2576
+ },
2577
+ estimatedSavings: {
2578
+ amount: savings,
2579
+ currency,
2580
+ percentOfSpend: monthly > 0 ? savings / monthly * 100 : 0
2581
+ },
2582
+ serviceGroups: arrayOrEmpty(
2583
+ source.service_family_breakdown ?? source.serviceGroups
2584
+ ).map((item, index) => {
2585
+ const row = isObject22(item) ? item : {};
2586
+ return {
2587
+ name: stringOr2(row.name ?? row.service ?? row.family, `Service ${index + 1}`),
2588
+ amount: numberOr(row.amount ?? row.monthly_cost ?? row.cost, 0),
2589
+ currency: stringOr2(row.currency, currency),
2590
+ changePercent: numberOr(row.change_percent ?? row.changePercent, 0)
2591
+ };
2592
+ }),
2593
+ recommendations: arrayOrEmpty(
2594
+ source.recommendations ?? source.top_opportunities ?? source.opportunities
2595
+ ).map((item, index) => {
2596
+ const row = isObject22(item) ? item : {};
2597
+ return {
2598
+ id: stringOr2(row.id ?? row.resource_id, `recommendation-${index + 1}`),
2599
+ title: stringOr2(row.title ?? row.recommendation ?? row.description, "Cost recommendation"),
2600
+ monthlySavings: numberOr(row.monthlySavings ?? row.monthly_savings ?? row.savings, 0),
2601
+ currency: stringOr2(row.currency, currency),
2602
+ risk: stringOr2(row.risk, "medium")
2603
+ };
2604
+ }),
2605
+ anomalies: arrayOrEmpty(source.anomalies),
2606
+ budgets: arrayOrEmpty(source.budgets),
2607
+ trend: arrayOrEmpty(source.trend ?? source.time_series)
2608
+ };
2609
+ };
2610
+ var normalizeWafParsed = (report, metrics = {}) => {
2611
+ const parsed = isObject22(report.parsed) ? report.parsed : void 0;
2612
+ if (parsed && isObject22(parsed.score) && isObject22(parsed.counts)) {
2613
+ return parsed;
2614
+ }
2615
+ const processed = isObject22(report.processed) ? report.processed : {};
2616
+ const source = { ...metrics, ...processed };
2617
+ const pillarScores = isObject22(source.pillar_scores) ? source.pillar_scores : {};
2618
+ const rules = arrayOrEmpty(report.all_rules ?? source.rules).map((item, index) => {
2619
+ const row = isObject22(item) ? item : {};
2620
+ const outcome = stringOr2(row.status ?? row.outcome, "pass").toLowerCase();
2621
+ return {
2622
+ id: stringOr2(row.id ?? row.rule_id ?? row.name, `rule-${index + 1}`),
2623
+ pillar: stringOr2(row.pillar, "Uncategorized"),
2624
+ title: stringOr2(row.title ?? row.description, "Architecture rule"),
2625
+ status: outcome === "fail" || outcome === "error" ? "fail" : outcome === "warn" ? "warn" : "pass",
2626
+ severity: stringOr2(row.severity, "medium").toLowerCase(),
2627
+ resource: typeof row.resource === "string" ? row.resource : void 0,
2628
+ evidence: typeof row.evidence === "string" ? row.evidence : void 0,
2629
+ recommendation: typeof row.recommendation === "string" ? row.recommendation : void 0
2630
+ };
2631
+ });
2632
+ const failedRules = rules.filter((rule) => rule.status === "fail");
2633
+ const mediumRules = rules.filter((rule) => rule.severity === "medium");
2634
+ const highRules = rules.filter(
2635
+ (rule) => rule.severity === "high" || rule.severity === "critical"
2636
+ );
2637
+ return {
2638
+ score: {
2639
+ overall: numberOr(source.overall_score, 0),
2640
+ pillars: Object.entries(pillarScores).map(([id, score]) => ({
2641
+ id,
2642
+ label: id,
2643
+ score: numberOr(score, 0),
2644
+ passed: 0,
2645
+ warned: 0,
2646
+ failed: 0
2647
+ }))
2648
+ },
2649
+ counts: {
2650
+ passed: rules.length - failedRules.length,
2651
+ highRisk: numberOr(source.high_count, highRules.length),
2652
+ mediumRisk: numberOr(source.medium_count, mediumRules.length),
2653
+ evidenceCoveragePercent: numberOr(source.evidence_coverage_percent, 0)
2654
+ },
2655
+ rules
2656
+ };
2657
+ };
2658
+ var normalizeBackendReportDetail = (input, fallback) => {
2659
+ if (!isObject22(input)) {
2660
+ return normalizeReportEnvelope(input);
2661
+ }
2662
+ const report = isObject22(input.report) ? input.report : input;
2663
+ const reportType = input.report_type ?? report.kind ?? fallback.reportType;
2664
+ const kind = reportTypeToKind(reportType);
2665
+ const projectId = stringOr2(input.project_id ?? report.project_id, fallback.projectId);
2666
+ const generatedAt = stringOr2(
2667
+ input.timestamp ?? report.generated_at ?? (isObject22(report.metadata) ? report.metadata.generated_at : void 0),
2668
+ (/* @__PURE__ */ new Date(0)).toISOString()
2669
+ );
2670
+ const parsed = kind === "waf" ? normalizeWafParsed(report) : normalizeCostParsed(report);
2671
+ return {
2672
+ id: stringOr2(report.id, `${kind}:latest:${projectId}`),
2673
+ kind,
2674
+ projectId,
2675
+ generatedAt,
2676
+ source: { provider: "azure" },
2677
+ raw: report,
2678
+ parsed,
2679
+ formatted: isObject22(report.formatted) ? normalizeReportEnvelope({ ...report, kind, project_id: projectId }).formatted : void 0
2680
+ };
2681
+ };
2682
+ var normalizeBackendHistoryItem = (input) => {
2683
+ if (!isObject22(input)) {
2684
+ return normalizeReportEnvelope(input);
2685
+ }
2686
+ const kind = reportTypeToKind(input.report_type);
2687
+ const projectId = stringOr2(input.project_id, "unknown-project");
2688
+ const generatedAt = stringOr2(input.generated_at, (/* @__PURE__ */ new Date(0)).toISOString());
2689
+ const metrics = isObject22(input.metrics) ? input.metrics : {};
2690
+ return {
2691
+ id: stringOr2(input.report_id ?? input.id, `${kind}:${generatedAt}`),
2692
+ kind,
2693
+ projectId,
2694
+ generatedAt,
2695
+ source: { provider: "azure" },
2696
+ raw: input,
2697
+ parsed: kind === "waf" ? normalizeWafParsed({}, metrics) : normalizeCostParsed({}, metrics),
2698
+ formatted: {
2699
+ title: `${kind === "waf" ? "Well-Architected Framework" : "Cost"} report`,
2700
+ summary: stringOr2(input.status, "Report available"),
2701
+ sections: []
2702
+ }
2703
+ };
2704
+ };
2705
+ var normalizeBackendHistory = (input) => {
2706
+ const items = isObject22(input) && Array.isArray(input.items) ? input.items : input;
2707
+ if (!Array.isArray(items)) {
2708
+ return normalizeReportList(input);
2709
+ }
2710
+ return items.map((item) => normalizeBackendHistoryItem(item));
2711
+ };
2712
+ var fetchJson = async (options, path2, query = {}) => {
2713
+ const apiBase = normalizeApiBase(options.baseUrl);
2714
+ const url = appendQuery(new URL(`${apiBase}${path2}`), query);
2715
+ const response = await fetch(url, {
2716
+ method: "GET",
2717
+ headers: getCLIHeaders(options.authToken)
2718
+ });
2719
+ if (!response.ok) {
2720
+ const body = await compactErrorBody2(response);
2721
+ throw new Error(
2722
+ `Report request failed with status ${response.status} ${response.statusText}${body ? `: ${body}` : ""}`
2723
+ );
2724
+ }
2725
+ return response.json();
2726
+ };
2727
+ var postJson = async (options, path2, query = {}, body) => {
2728
+ const apiBase = normalizeApiBase(options.baseUrl);
2729
+ const url = appendQuery(new URL(`${apiBase}${path2}`), query);
2730
+ const response = await fetch(url, {
2731
+ method: "POST",
2732
+ headers: withIdempotencyHeader({
2733
+ ...getCLIHeaders(options.authToken),
2734
+ "content-type": "application/json"
2735
+ }),
2736
+ body: body === void 0 ? void 0 : JSON.stringify(body)
2737
+ });
2738
+ if (!response.ok) {
2739
+ const bodyText = await compactErrorBody2(response);
2740
+ throw new Error(
2741
+ `Report request failed with status ${response.status} ${response.statusText}${bodyText ? `: ${bodyText}` : ""}`
2742
+ );
2743
+ }
2744
+ return response.json();
2745
+ };
2746
+ var fetchReportResource = async (options, path2, query = {}) => fetchJson(options, path2, query);
2747
+ var listReports = async (options) => {
2748
+ const raw = await fetchJson(options, "/reports/history", {
2749
+ user_id: requireUserId(options.userId),
2750
+ project_ids: requireProjectId(options.projectId),
2751
+ report_type: backendReportType(options.kind)
2752
+ });
2753
+ return normalizeBackendHistory(raw).filter((report) => {
2754
+ if (!options.kind || options.kind === "all") return true;
2755
+ return report.kind === options.kind;
2756
+ });
2757
+ };
2758
+ var getReport = async (options) => {
2759
+ if (options.reportId.startsWith("latest:") || options.reportId.startsWith("history:")) {
2760
+ const reports = await listReports({
2761
+ baseUrl: options.baseUrl,
2762
+ authToken: options.authToken,
2763
+ projectId: options.projectId,
2764
+ userId: options.userId,
2765
+ kind: "all"
2766
+ });
2767
+ const found = reports.find((report) => report.id === options.reportId);
2768
+ if (found) {
2769
+ const reportType = found.kind === "waf" ? "architecture" : "cost";
2770
+ return normalizeBackendReportDetail(
2771
+ await fetchJson(
2772
+ options,
2773
+ `/reports/detail/${encodeURIComponent(found.projectId)}/${reportType}`,
2774
+ {
2775
+ user_id: requireUserId(options.userId),
2776
+ timestamp: found.raw && isObject22(found.raw) && !found.raw.is_latest ? found.generatedAt : void 0
2777
+ }
2778
+ ),
2779
+ { projectId: found.projectId, reportType }
2780
+ );
2781
+ }
2782
+ }
2783
+ const raw = await fetchJson(
2784
+ options,
2785
+ `/reports/${encodeURIComponent(options.reportId)}`,
2786
+ { project_id: options.projectId, view: options.view }
2787
+ );
2788
+ return normalizeReportEnvelope(raw);
2789
+ };
2790
+ var getCostReport = async (options) => {
2791
+ const projectId = requireProjectId(options.projectId);
2792
+ const raw = await fetchJson(
2793
+ options,
2794
+ `/reports/detail/${encodeURIComponent(projectId)}/cost`,
2795
+ { user_id: requireUserId(options.userId) }
2796
+ );
2797
+ return normalizeBackendReportDetail(raw, { projectId, reportType: "cost" });
2798
+ };
2799
+ var getWafReport = async (options) => {
2800
+ const projectId = requireProjectId(options.projectId);
2801
+ const raw = await fetchJson(
2802
+ options,
2803
+ `/reports/detail/${encodeURIComponent(projectId)}/architecture`,
2804
+ { user_id: requireUserId(options.userId) }
2805
+ );
2806
+ const report = normalizeBackendReportDetail(raw, { projectId, reportType: "architecture" });
2807
+ if (options.severity && isObject22(report.parsed) && Array.isArray(report.parsed.rules)) {
2808
+ return {
2809
+ ...report,
2810
+ parsed: {
2811
+ ...report.parsed,
2812
+ rules: report.parsed.rules.filter(
2813
+ (rule) => isObject22(rule) && String(rule.severity).toLowerCase() === options.severity?.toLowerCase()
2814
+ )
2815
+ }
2816
+ };
2817
+ }
2818
+ return report;
2819
+ };
2820
+ var getReportDetail = async (options) => fetchJson(
2821
+ options,
2822
+ `/reports/detail/${encodeURIComponent(options.projectId)}/${encodeURIComponent(
2823
+ options.reportType
2824
+ )}`,
2825
+ {
2826
+ user_id: options.userId,
2827
+ timestamp: options.timestamp
2828
+ }
2829
+ );
2830
+ var getCostReportFull = async (options) => fetchJson(options, `/cost-reports/${encodeURIComponent(options.projectId)}/full`, {
2831
+ user_id: options.userId
2832
+ });
2833
+ var getWafReportFull = async (options) => fetchJson(
2834
+ options,
2835
+ `/well-architected-reports/${encodeURIComponent(options.projectId)}/full`,
2836
+ {
2837
+ user_id: options.userId
2838
+ }
2839
+ );
2840
+ var getCostReportHistory = async (options) => options.timestamp ? fetchJson(
2841
+ options,
2842
+ `/cost-reports/${encodeURIComponent(options.projectId)}/historical/${encodeURIComponent(
2843
+ options.timestamp
2844
+ )}`,
2845
+ { user_id: options.userId }
2846
+ ) : fetchJson(options, `/cost-reports/${encodeURIComponent(options.projectId)}/historical`, {
2847
+ user_id: options.userId
2848
+ });
2849
+ var getWafReportHistory = async (options) => options.timestamp ? fetchJson(
2850
+ options,
2851
+ `/well-architected-reports/${encodeURIComponent(
2852
+ options.projectId
2853
+ )}/history/${encodeURIComponent(options.timestamp)}`,
2854
+ { user_id: options.userId }
2855
+ ) : fetchJson(
2856
+ options,
2857
+ `/well-architected-reports/${encodeURIComponent(options.projectId)}/history`,
2858
+ { user_id: options.userId }
2859
+ );
2860
+ var reportRunTypes = (type) => {
2861
+ if (type === "all") return ["cost", "waf", "unit-tests"];
2862
+ if (type === "architecture") return ["waf"];
2863
+ return [type];
2864
+ };
2865
+ var boolQuery = (value) => value === void 0 ? void 0 : String(Boolean(value));
2866
+ var runReports = async (options) => {
2867
+ const projectId = requireProjectId(options.projectId);
2868
+ const userId = requireUserId(options.userId);
2869
+ const results = [];
2870
+ for (const type of reportRunTypes(options.type)) {
2871
+ if (type === "cost") {
2872
+ results.push(
2873
+ await postJson(options, `/cost-reports/${encodeURIComponent(projectId)}/regenerate`, {
2874
+ user_id: userId,
2875
+ region: options.region,
2876
+ currency: options.currency,
2877
+ include_time_series: boolQuery(options.includeTimeSeries),
2878
+ save_report: boolQuery(options.saveReport)
2879
+ })
2880
+ );
2881
+ continue;
2882
+ }
2883
+ if (type === "waf") {
2884
+ results.push(
2885
+ await postJson(
2886
+ options,
2887
+ `/well-architected-reports/${encodeURIComponent(projectId)}/regenerate`,
2888
+ {
2889
+ user_id: userId,
2890
+ save_report: boolQuery(options.saveReport)
2891
+ }
2892
+ )
2893
+ );
2894
+ continue;
2895
+ }
2896
+ results.push(
2897
+ await postJson(options, `/reports/${encodeURIComponent(projectId)}/unit-tests/regenerate`, {
2898
+ user_id: userId
2899
+ })
2900
+ );
2901
+ }
2902
+ return results;
2903
+ };
2904
+ var getReportJobStatus = async (options) => fetchJson(options, `/jobs/${encodeURIComponent(options.jobId)}`, {
2905
+ user_id: requireUserId(options.userId)
2906
+ });
2907
+ var CREDIT_LOW_RATIO = 0.1;
2908
+ var CREDIT_WARNING_RATIO = 0.25;
2909
+ var DEFAULT_FREE_TRIAL_CREDITS_TOTAL = 1e3;
2910
+ var fetchBillingJson = async (options, path2, query = {}, request = {}) => {
2911
+ const url = new URL(`${normalizeApiBase(options.baseUrl)}${path2}`);
2912
+ for (const [key, value] of Object.entries(query)) {
2913
+ if (value !== void 0 && value !== "") {
2914
+ url.searchParams.set(key, String(value));
2915
+ }
2916
+ }
2917
+ const headers = request.method === "POST" ? withIdempotencyHeader(getCLIHeaders(options.authToken)) : getCLIHeaders(options.authToken);
2918
+ if (request.body) {
2919
+ headers["content-type"] = "application/json";
2920
+ }
2921
+ const response = await fetch(url, {
2922
+ method: request.method ?? "GET",
2923
+ headers,
2924
+ ...request.body ? { body: JSON.stringify(request.body) } : {}
2925
+ });
2926
+ if (!response.ok) {
2927
+ const body = await response.text().catch(() => "");
2928
+ const redactedBody = redactSensitiveText(body.trim());
2929
+ throw new Error(
2930
+ `Billing request failed with status ${response.status} ${response.statusText}${redactedBody ? `: ${redactedBody}` : ""}`
2931
+ );
2932
+ }
2933
+ return await response.json();
2934
+ };
2935
+ var unwrapData = (value) => {
2936
+ if (value && typeof value === "object" && "data" in value && value.data) {
2937
+ return value.data;
2938
+ }
2939
+ return value;
2940
+ };
2941
+ var getBillingConfig = (options) => fetchBillingJson(options, "/billing/config");
2942
+ var getBillingEntitlement = async (options) => unwrapData(await fetchBillingJson(
2943
+ options,
2944
+ "/billing/entitlement"
2945
+ ));
2946
+ var getSubscriptionStatus = (options) => fetchBillingJson(options, "/billing/subscription/status");
2947
+ var getSubscriptionBillingInfo = (options) => fetchBillingJson(options, "/billing/subscription/billing-info", {
2948
+ limit: Math.max(1, Math.min(50, Number(options.limit ?? 20)))
2949
+ });
2950
+ var getTopUpPacks = (options) => fetchBillingJson(options, "/billing/top-up/packs");
2951
+ var createTopUpCheckoutSession = (options) => fetchBillingJson(
2952
+ options,
2953
+ "/billing/checkout/session/top-up",
2954
+ {},
2955
+ {
2956
+ method: "POST",
2957
+ body: {
2958
+ pack_id: options.packId,
2959
+ ...options.returnTo ? { return_to: options.returnTo } : {},
2960
+ ...options.preferredCurrency ? { preferred_currency: options.preferredCurrency } : {},
2961
+ ...options.countryCode ? { country_code: options.countryCode } : {},
2962
+ ...options.contactEmail ? { contact_email: options.contactEmail } : {},
2963
+ ...options.contactPhone ? { contact_phone: options.contactPhone } : {},
2964
+ ...options.contactCountryCode ? { contact_country_code: options.contactCountryCode } : {}
2965
+ }
2966
+ }
2967
+ );
2968
+ var getBillingNotifications = (options) => fetchBillingJson(options, "/billing/notifications", {
2969
+ limit: Math.max(1, Math.min(100, Number(options.limit ?? 25)))
2970
+ });
2971
+ var getBillingUsageSummary = (options) => fetchBillingJson(options, "/billing/usage/summary", {
2972
+ start_at: options.startAt,
2973
+ end_at: options.endAt,
2974
+ granularity: options.granularity,
2975
+ action_type: options.actionType,
2976
+ model_name: options.modelName,
2977
+ outcome: options.outcome,
2978
+ trigger_mode: options.triggerMode,
2979
+ charge_status: options.chargeStatus
2980
+ });
2981
+ var getBillingUsageLedger = (options) => fetchBillingJson(options, "/billing/usage/ledger", {
2982
+ start_at: options.startAt,
2983
+ end_at: options.endAt,
2984
+ action_type: options.actionType,
2985
+ model_name: options.modelName,
2986
+ outcome: options.outcome,
2987
+ trigger_mode: options.triggerMode,
2988
+ charge_status: options.chargeStatus,
2989
+ limit: options.limit,
2990
+ cursor: options.cursor
2991
+ });
2992
+ var isFreePlan = (plan) => {
2993
+ if (!plan) {
2994
+ return false;
2995
+ }
2996
+ const id = String(plan.id || "").toLowerCase();
2997
+ const name = String(plan.name || "").toLowerCase();
2998
+ return id === "free" || name === "free" || Number(plan.price_usd ?? NaN) === 0;
2999
+ };
3000
+ var getCreditStatus = (summary, options) => {
3001
+ if (!summary) {
3002
+ return null;
3003
+ }
3004
+ const planName = summary.plan?.name || "Plan";
3005
+ const planId = String(summary.plan?.id || "").toLowerCase();
3006
+ const normalizedPlanName = String(summary.plan?.name || "").toLowerCase();
3007
+ const planSource = String(summary.plan_source || "").toLowerCase();
3008
+ const isFreeLikePlan = isFreePlan(summary.plan) || planSource === "trial" || planSource === "free" || planSource === "free_blocked";
3009
+ const explicitUnlimited = planId.includes("enterprise") || normalizedPlanName.includes("enterprise");
3010
+ const balance = summary.balance || {};
3011
+ const cycleTotal = Math.max(
3012
+ Number(balance.credits_total_cycle ?? balance.credits_total ?? 0),
3013
+ 0
3014
+ );
3015
+ const cycleRemaining = Math.max(
3016
+ Number(balance.credits_remaining_cycle ?? balance.credits_remaining ?? 0),
3017
+ 0
3018
+ );
3019
+ const cycleUsed = Math.max(
3020
+ Number.isFinite(Number(balance.credits_used_cycle ?? balance.credits_used)) ? Number(balance.credits_used_cycle ?? balance.credits_used) : cycleTotal - cycleRemaining,
3021
+ 0
3022
+ );
3023
+ const topUpBalance = Math.max(
3024
+ Number(balance.top_up_credits_balance ?? summary.top_up_credits_balance ?? 0),
3025
+ 0
3026
+ );
3027
+ const fallbackEffectiveTotal = cycleTotal + topUpBalance;
3028
+ const fallbackEffectiveRemaining = cycleRemaining + topUpBalance;
3029
+ const reportedEffectiveTotal = Number(balance.credits_total_effective ?? NaN);
3030
+ const reportedEffectiveRemaining = Number(
3031
+ balance.credits_remaining_effective ?? summary.credits_remaining_total ?? NaN
3032
+ );
3033
+ let effectiveTotal = Number.isFinite(reportedEffectiveTotal) ? Math.max(reportedEffectiveTotal, fallbackEffectiveTotal, 0) : fallbackEffectiveTotal;
3034
+ const effectiveRemaining = Number.isFinite(reportedEffectiveRemaining) ? Math.max(reportedEffectiveRemaining, fallbackEffectiveRemaining, 0) : fallbackEffectiveRemaining;
3035
+ if (effectiveTotal <= 0) {
3036
+ effectiveTotal = fallbackEffectiveTotal;
3037
+ }
3038
+ if (effectiveRemaining > effectiveTotal) {
3039
+ effectiveTotal = effectiveRemaining;
3040
+ }
3041
+ const trialTotal = Math.max(
3042
+ Number(
3043
+ summary.trial_state?.credits_total ?? summary.trial_state?.initial_credits ?? summary.plan?.features?.trial_credits_total ?? DEFAULT_FREE_TRIAL_CREDITS_TOTAL
3044
+ ),
3045
+ 0
3046
+ );
3047
+ const useTrialDisplayTotal = isFreeLikePlan && effectiveTotal <= 0 && trialTotal > 0;
3048
+ const displayTotal = useTrialDisplayTotal ? trialTotal : effectiveTotal;
3049
+ const remainingCandidate = useTrialDisplayTotal && (summary.trial_state?.consumed || summary.trial_state?.blocked) ? 0 : effectiveRemaining;
3050
+ const remaining = displayTotal > 0 ? Math.min(Math.max(remainingCandidate, 0), displayTotal) : 0;
3051
+ const used = displayTotal > 0 ? Math.max(displayTotal - remaining, 0) : 0;
3052
+ const remainingRatio = displayTotal > 0 ? remaining / displayTotal : explicitUnlimited ? 1 : 0;
3053
+ const creditsPerEvent = options?.creditsPerEvent;
3054
+ const messagesRemaining = explicitUnlimited || !creditsPerEvent || creditsPerEvent <= 0 ? null : Math.max(0, Math.floor(remaining / creditsPerEvent));
3055
+ const exhausted = summary.credits_exhausted === true || !explicitUnlimited && remaining <= 0;
3056
+ const low = !exhausted && (summary.credits_low === true || remainingRatio <= CREDIT_LOW_RATIO);
3057
+ const warning = !exhausted && !low && remainingRatio <= CREDIT_WARNING_RATIO;
3058
+ const tone = exhausted ? "exhausted" : low ? "low" : warning ? "warning" : "normal";
3059
+ return {
3060
+ planName,
3061
+ remaining,
3062
+ total: displayTotal,
3063
+ used,
3064
+ cycleTotal,
3065
+ cycleUsed,
3066
+ cycleRemaining,
3067
+ effectiveTotal,
3068
+ effectiveRemaining,
3069
+ topUpBalance,
3070
+ remainingRatio,
3071
+ isUnlimited: explicitUnlimited,
3072
+ tone,
3073
+ messagesRemaining
3074
+ };
3075
+ };
3076
+ var providerValues = /* @__PURE__ */ new Set(["azure", "aws", "gcp"]);
3077
+ var sanitizeNamePart = (value) => value.replace(/\.(json|yaml|yml|tf)$/i, "").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
3078
+ var inferCloudProvider = (owner, repo, filePath) => {
3079
+ const haystack = `${owner}/${repo}/${filePath}`.toLowerCase();
3080
+ if (haystack.includes("aws") || haystack.includes("cloudformation")) {
3081
+ return "aws";
3082
+ }
3083
+ if (haystack.includes("gcp") || haystack.includes("google")) {
3084
+ return "gcp";
3085
+ }
3086
+ return "azure";
3087
+ };
3088
+ var generateProjectName = (filePath, repo) => {
3089
+ const parts = filePath.split("/").filter(Boolean);
3090
+ const file = parts[parts.length - 1] || repo;
3091
+ const parent = parts.length > 1 ? parts[parts.length - 2] : "";
3092
+ if (/^azuredeploy\.json$/i.test(file) && parent) {
3093
+ return sanitizeNamePart(parent);
3094
+ }
3095
+ return sanitizeNamePart(file || repo) || repo;
3096
+ };
3097
+ var parseTemplateUrl = (value) => {
3098
+ try {
3099
+ const url = new URL(value);
3100
+ const parts = url.pathname.split("/").filter(Boolean);
3101
+ if (url.hostname === "raw.githubusercontent.com") {
3102
+ if (parts.length < 3) {
3103
+ return null;
3104
+ }
3105
+ const [owner2, repo2, branch2, ...fileParts] = parts;
3106
+ const filePath2 = fileParts.join("/");
3107
+ return {
3108
+ normalizedUrl: value,
3109
+ githubUrl: `https://github.com/${owner2}/${repo2}/blob/${branch2}/${filePath2}`,
3110
+ cloudProvider: inferCloudProvider(owner2, repo2, filePath2),
3111
+ suggestedName: generateProjectName(filePath2, repo2),
3112
+ suggestedDescription: `Template from ${owner2}/${repo2}`,
3113
+ owner: owner2,
3114
+ repo: repo2,
3115
+ branch: branch2,
3116
+ filePath: filePath2
3117
+ };
3118
+ }
3119
+ if (url.hostname !== "github.com" || parts.length < 4) {
3120
+ return null;
3121
+ }
3122
+ const [owner, repo, type, branch, ...rest] = parts;
3123
+ if (type !== "blob" && type !== "tree") {
3124
+ return null;
3125
+ }
3126
+ const rawRest = rest.join("/");
3127
+ const filePath = rawRest && /\.(json|yaml|yml|tf)$/i.test(rawRest) ? rawRest : rawRest ? `${rawRest}/azuredeploy.json` : "azuredeploy.json";
3128
+ return {
3129
+ normalizedUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`,
3130
+ githubUrl: value,
3131
+ cloudProvider: inferCloudProvider(owner, repo, filePath),
3132
+ suggestedName: generateProjectName(filePath, repo),
3133
+ suggestedDescription: `Template from ${owner}/${repo}`,
3134
+ owner,
3135
+ repo,
3136
+ branch,
3137
+ filePath
3138
+ };
3139
+ } catch {
3140
+ return null;
3141
+ }
3142
+ };
3143
+ var buildQuickProjectPayload = (input) => {
3144
+ const inferred = input.templateUrl ? parseTemplateUrl(input.templateUrl) : null;
3145
+ const normalizedTemplateUrl = inferred?.normalizedUrl ?? input.templateUrl;
3146
+ const provider = input.provider ?? inferred?.cloudProvider ?? "azure";
3147
+ if (!providerValues.has(provider)) {
3148
+ throw new Error(`Unsupported cloud provider '${provider}'.`);
3149
+ }
3150
+ const name = input.name?.trim() || inferred?.suggestedName || "Quick Project";
3151
+ const description = input.description?.trim() || inferred?.suggestedDescription || `Template project for ${name}`;
3152
+ const connection = {
3153
+ user_id: input.userId,
3154
+ name: `${name} Connection`,
3155
+ cloud_provider: provider,
3156
+ description,
3157
+ type: "template",
3158
+ ...normalizedTemplateUrl ? { template_url: normalizedTemplateUrl } : {},
3159
+ ...input.parametersUrl ? { parameters_file_url: input.parametersUrl } : {},
3160
+ auto_sync: true
3161
+ };
3162
+ const project = {
3163
+ user_id: input.userId,
3164
+ name,
3165
+ description,
3166
+ cloud_provider: provider,
3167
+ connection_ids: [],
3168
+ type: "template",
3169
+ report_config: {
3170
+ auto_generate_reports: true,
3171
+ include_cost_report: true,
3172
+ include_cost_forecast: true,
3173
+ region: "eastus",
3174
+ currency: "USD"
3175
+ }
3176
+ };
3177
+ return {
3178
+ connection,
3179
+ project,
3180
+ normalizedTemplateUrl,
3181
+ inferred
3182
+ };
3183
+ };
3184
+ var responseJson = async (response, label) => {
3185
+ if (!response.ok) {
3186
+ const body = await response.text().catch(() => "");
3187
+ const redactedBody = redactSensitiveText(body.trim());
3188
+ throw new Error(
3189
+ `${label} failed with status ${response.status} ${response.statusText}${redactedBody ? `: ${redactedBody}` : ""}`
3190
+ );
3191
+ }
3192
+ return await response.json();
3193
+ };
3194
+ var appendConnectionBody = (payload, input) => {
3195
+ if (!input.templateFile && !input.parametersFile) {
3196
+ return JSON.stringify(payload);
3197
+ }
3198
+ const formData = new FormData();
3199
+ formData.append("user_id", payload.user_id);
3200
+ formData.append("name", payload.name);
3201
+ formData.append("cloud_provider", payload.cloud_provider);
3202
+ formData.append("description", payload.description);
3203
+ formData.append("type", payload.type);
3204
+ formData.append("auto_sync", String(payload.auto_sync ?? true));
3205
+ if (payload.template_url) {
3206
+ formData.append("template_url", payload.template_url);
3207
+ }
3208
+ if (payload.parameters_file_url) {
3209
+ formData.append("parameters_file_url", payload.parameters_file_url);
3210
+ }
3211
+ if (input.templateFile) {
3212
+ formData.append("template_file", input.templateFile, input.templateFileName || "template.json");
3213
+ }
3214
+ if (input.parametersFile) {
3215
+ formData.append(
3216
+ "parameters_file",
3217
+ input.parametersFile,
3218
+ input.parametersFileName || "parameters.json"
3219
+ );
3220
+ }
3221
+ return formData;
3222
+ };
3223
+ var headersForBody = (authToken, body) => {
3224
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
3225
+ const headers = getCLIHeaders(authToken);
3226
+ delete headers["Content-Type"];
3227
+ return headers;
3228
+ }
3229
+ return getCLIHeaders(authToken);
3230
+ };
3231
+ var createQuickProject = async (input) => {
3232
+ if (!input.templateUrl && !input.templateFile) {
3233
+ throw new Error("Provide --template-url or --template-file.");
3234
+ }
3235
+ const built = buildQuickProjectPayload(input);
3236
+ const apiBase = normalizeApiBase(input.baseUrl);
3237
+ const connectionBody = appendConnectionBody(built.connection, input);
3238
+ const connection = await responseJson(
3239
+ await fetch(`${apiBase}/connection/`, {
3240
+ method: "POST",
3241
+ headers: withIdempotencyHeader(headersForBody(input.authToken, connectionBody)),
3242
+ body: connectionBody
3243
+ }),
3244
+ "Connection creation"
3245
+ );
3246
+ const connectionId = String(connection.id || "");
3247
+ if (!connectionId) {
3248
+ throw new Error("Connection creation did not return a connection id.");
3249
+ }
3250
+ const projectPayload = {
3251
+ ...built.project,
3252
+ connection_ids: [connectionId]
3253
+ };
3254
+ const project = await responseJson(
3255
+ await fetch(`${apiBase}/projects/`, {
3256
+ method: "POST",
3257
+ headers: withIdempotencyHeader(getCLIHeaders(input.authToken)),
3258
+ body: JSON.stringify(projectPayload)
3259
+ }),
3260
+ "Project creation"
3261
+ );
3262
+ return {
3263
+ project,
3264
+ connection,
3265
+ syncStatus: connection.sync_status ?? connection.sync_job ?? null,
3266
+ normalizedTemplateUrl: built.normalizedTemplateUrl,
3267
+ inferred: built.inferred
3268
+ };
3269
+ };
3270
+ var listConnections = async (options) => {
3271
+ const response = await fetch(
3272
+ `${normalizeApiBase(options.baseUrl)}/connection/user/${encodeURIComponent(options.userId)}`,
3273
+ { method: "GET", headers: getCLIHeaders(options.authToken) }
3274
+ );
3275
+ return responseJson(response, "List connections");
3276
+ };
3277
+ var getConnection = async (options) => {
3278
+ const connections = await listConnections(options);
3279
+ return connections.find((connection) => String(connection.id) === options.connectionId) ?? null;
3280
+ };
3281
+ var compactErrorBody3 = async (response) => {
3282
+ const body = await response.text().catch(() => "");
3283
+ const trimmed = body.trim();
3284
+ return trimmed ? redactSensitiveText(trimmed).slice(0, 1e3) : void 0;
3285
+ };
3286
+ var fetchCredentialJson = async (options, path2, request = {}) => {
3287
+ const url = new URL(`${normalizeApiBase(options.baseUrl)}${path2}`);
3288
+ for (const [key, value] of Object.entries(request.query ?? {})) {
3289
+ if (value) {
3290
+ url.searchParams.set(key, value);
3291
+ }
3292
+ }
3293
+ const headers = request.method === "POST" ? withIdempotencyHeader(getCLIHeaders(options.authToken), request.idempotencyKey) : getCLIHeaders(options.authToken);
3294
+ const response = await fetch(url, {
3295
+ method: request.method ?? "GET",
3296
+ headers,
3297
+ ...request.body ? { body: JSON.stringify(request.body) } : {}
3298
+ });
3299
+ if (!response.ok) {
3300
+ const body = await compactErrorBody3(response);
3301
+ throw new Error(
3302
+ `Credential request failed with status ${response.status} ${response.statusText}${body ? `: ${body}` : ""}`
3303
+ );
3304
+ }
3305
+ return await response.json();
3306
+ };
3307
+ var getCredentialTemplates = (options) => fetchCredentialJson(options, "/credential-templates");
3308
+ var listCredentials = (options) => fetchCredentialJson(options, "/credentials", {
3309
+ query: { project_id: options.projectId }
3310
+ });
3311
+ var createCredential = (options) => fetchCredentialJson(options, "/credentials", {
3312
+ method: "POST",
3313
+ idempotencyKey: options.idempotencyKey,
3314
+ body: {
3315
+ template: options.template,
3316
+ name: options.name,
3317
+ ...options.projectId ? { project_id: options.projectId } : {},
3318
+ ...options.expires ? { expires: options.expires } : {},
3319
+ ...options.capabilities?.length ? { capabilities: options.capabilities } : {}
3320
+ }
3321
+ });
3322
+ var getCredential = (options) => fetchCredentialJson(
3323
+ options,
3324
+ `/credentials/${encodeURIComponent(options.credentialId)}`
3325
+ );
3326
+ var revokeCredential = (options) => fetchCredentialJson(
3327
+ options,
3328
+ `/credentials/${encodeURIComponent(options.credentialId)}/revoke`,
3329
+ {
3330
+ method: "POST",
3331
+ idempotencyKey: options.idempotencyKey,
3332
+ body: options.reason ? { reason: options.reason } : {}
3333
+ }
3334
+ );
3335
+ var getIdentity = (options) => fetchCredentialJson(options, "/identity");
3336
+ var getCapabilities = (options) => fetchCredentialJson(options, "/capabilities");
3337
+ var AgentProfileRequestError = class extends Error {
3338
+ status;
3339
+ statusText;
3340
+ body;
3341
+ code;
3342
+ requiresAuth;
3343
+ constructor(input) {
3344
+ super(
3345
+ `Agent Profile request failed with status ${input.status} ${input.statusText}${input.body.trim() ? `: ${input.body.trim().slice(0, 1e3)}` : ""}`
3346
+ );
3347
+ this.name = "AgentProfileRequestError";
3348
+ this.status = input.status;
3349
+ this.statusText = input.statusText;
3350
+ this.body = input.body;
3351
+ this.code = input.code;
3352
+ this.requiresAuth = input.requiresAuth;
3353
+ }
3354
+ };
3355
+ var parseErrorBody = (body) => {
3356
+ try {
3357
+ const parsed = JSON.parse(body);
3358
+ return {
3359
+ code: typeof parsed.code === "string" ? parsed.code : void 0,
3360
+ requiresAuth: typeof parsed.requiresAuth === "boolean" ? parsed.requiresAuth : void 0
3361
+ };
3362
+ } catch {
3363
+ return {};
3364
+ }
3365
+ };
3366
+ var isAgentProfileAuthRequiredError = (error) => {
3367
+ if (!(error instanceof AgentProfileRequestError)) {
3368
+ return false;
3369
+ }
3370
+ return error.requiresAuth === true || error.code === "AUTH_REQUIRED_PUBLIC" || error.status === 401 || error.status === 403;
3371
+ };
3372
+ var isAgentProfileDiscoveryFallbackError = (error) => {
3373
+ if (isAgentProfileAuthRequiredError(error)) {
3374
+ return true;
3375
+ }
3376
+ return error instanceof AgentProfileRequestError && error.status === 404;
3377
+ };
3378
+ var fetchAgentProfileJson = async (options, path2) => {
3379
+ const response = await fetch(`${normalizeApiBase(options.baseUrl)}${path2}`, {
3380
+ headers: getCLIHeaders(options.authToken)
3381
+ });
3382
+ if (!response.ok) {
3383
+ const body = await response.text().catch(() => "");
3384
+ const parsed = parseErrorBody(body);
3385
+ throw new AgentProfileRequestError({
3386
+ status: response.status,
3387
+ statusText: response.statusText,
3388
+ body,
3389
+ code: parsed.code,
3390
+ requiresAuth: parsed.requiresAuth
3391
+ });
3392
+ }
3393
+ return await response.json();
3394
+ };
3395
+ var listAgentProfiles = (options) => fetchAgentProfileJson(options, "/agent-profiles");
3396
+ var getAgentProfile = (options) => fetchAgentProfileJson(
3397
+ options,
3398
+ `/agent-profiles/${encodeURIComponent(options.profileId)}`
3399
+ );
3400
+
3401
+ export {
3402
+ normalizeReportEnvelope,
3403
+ normalizeReportList,
3404
+ SECRET_REDACTION,
3405
+ isSensitiveSecretKey,
3406
+ redactSensitiveText,
3407
+ redactSensitiveSecrets,
3408
+ BUNDLED_AGENT_PROFILES,
3409
+ getBundledAgentProfiles,
3410
+ getBundledAgentProfile,
3411
+ assertSecureBaseUrl,
3412
+ normalizeApiBase,
3413
+ clearAuthSession,
3414
+ getCLIHeaders,
3415
+ getProjects,
3416
+ getAccessibleProjects,
3417
+ ensurePlaygroundProject,
3418
+ ensureDefaultProject,
3419
+ loginWithDeviceCode,
3420
+ login,
3421
+ logout,
3422
+ getAuthStatus,
3423
+ extractEmailFromToken,
3424
+ isAuthLookupFailure,
3425
+ checkUserStatus,
3426
+ completeOnboarding,
3427
+ getAuthToken,
3428
+ getAuthHeader,
3429
+ streamChat,
3430
+ initialChatState,
3431
+ completeActiveAssistantMessage,
3432
+ reduceChunk,
3433
+ fetchReportResource,
3434
+ listReports,
3435
+ getReport,
3436
+ getCostReport,
3437
+ getWafReport,
3438
+ getReportDetail,
3439
+ getCostReportFull,
3440
+ getWafReportFull,
3441
+ getCostReportHistory,
3442
+ getWafReportHistory,
3443
+ runReports,
3444
+ getReportJobStatus,
3445
+ getBillingConfig,
3446
+ getBillingEntitlement,
3447
+ getSubscriptionStatus,
3448
+ getSubscriptionBillingInfo,
3449
+ getTopUpPacks,
3450
+ createTopUpCheckoutSession,
3451
+ getBillingNotifications,
3452
+ getBillingUsageSummary,
3453
+ getBillingUsageLedger,
3454
+ getCreditStatus,
3455
+ parseTemplateUrl,
3456
+ buildQuickProjectPayload,
3457
+ createQuickProject,
3458
+ listConnections,
3459
+ getConnection,
3460
+ getCredentialTemplates,
3461
+ listCredentials,
3462
+ createCredential,
3463
+ getCredential,
3464
+ revokeCredential,
3465
+ getIdentity,
3466
+ getCapabilities,
3467
+ AgentProfileRequestError,
3468
+ isAgentProfileAuthRequiredError,
3469
+ isAgentProfileDiscoveryFallbackError,
3470
+ listAgentProfiles,
3471
+ getAgentProfile
3472
+ };