@ganakailabs/cloudeval-cli 0.20.3 → 0.21.1

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.
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-RCRNSEQS.js";
9
9
  import {
10
10
  Onboarding
11
- } from "./chunk-2GTSKMHA.js";
11
+ } from "./chunk-Q5D5HYWW.js";
12
12
  import {
13
13
  checkUserStatus,
14
14
  completeActiveAssistantMessage,
@@ -35,17 +35,17 @@ import {
35
35
  reduceChunk,
36
36
  runReports,
37
37
  streamChat
38
- } from "./chunk-2D4BE3OS.js";
38
+ } from "./chunk-LDDHLUZH.js";
39
39
  import {
40
40
  Banner
41
- } from "./chunk-7B3KVSVK.js";
41
+ } from "./chunk-6AHSCVTF.js";
42
42
  import {
43
43
  CLI_VERSION
44
- } from "./chunk-TCNIIMWL.js";
44
+ } from "./chunk-74TONCUI.js";
45
45
  import {
46
46
  raisedButtonStyle,
47
47
  terminalTheme
48
- } from "./chunk-QKZCKI55.js";
48
+ } from "./chunk-ZDKRIOMB.js";
49
49
 
50
50
  // src/ui/App.tsx
51
51
  import React8, { useEffect as useEffect4, useMemo, useState as useState4, startTransition } from "react";
@@ -53,14 +53,49 @@ import { Box as Box9, Text as Text10, useApp, useInput as useInput4 } from "ink"
53
53
  import { ScrollView } from "ink-scroll-view";
54
54
  import { randomUUID as randomUUID2 } from "crypto";
55
55
  import { spawn } from "child_process";
56
- import { mkdirSync, writeFileSync } from "fs";
57
- import { join, resolve } from "path";
56
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
57
+ import { join as join2, resolve as resolve2 } from "path";
58
58
 
59
59
  // src/ui/components/Loader.tsx
60
60
  import { useEffect, useState } from "react";
61
- import { Box, Text } from "ink";
62
- import { jsx, jsxs } from "react/jsx-runtime";
63
- var asciiFrames = ["[. ]", "[..]", "[--]", "[ ]"];
61
+ import { Box, Text as Text2 } from "ink";
62
+
63
+ // src/ui/components/Spinner.tsx
64
+ import React from "react";
65
+ import { Text } from "ink";
66
+ import { jsx } from "react/jsx-runtime";
67
+ var spinnerFrames = ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"];
68
+ var SPINNER_FRAME_INTERVAL_MS = 1e3;
69
+ var shouldAnimateSpinner = (animate = true) => animate;
70
+ var getSpinnerFrames = (_type = "line") => spinnerFrames;
71
+ var getLoaderStepMarker = (state, frameIndex = 0) => {
72
+ if (state === "complete") {
73
+ return "\u2713";
74
+ }
75
+ if (state === "pending") {
76
+ return "\xB7";
77
+ }
78
+ const frames = getSpinnerFrames("line");
79
+ return frames[Math.abs(frameIndex) % frames.length] ?? frames[0];
80
+ };
81
+ var Spinner = ({ type = "dots", animate = true }) => {
82
+ const [frame, setFrame] = React.useState(0);
83
+ const frames = getSpinnerFrames(type);
84
+ React.useEffect(() => {
85
+ if (!shouldAnimateSpinner(animate)) {
86
+ setFrame(0);
87
+ return;
88
+ }
89
+ const id = setInterval(() => {
90
+ setFrame((f) => (f + 1) % frames.length);
91
+ }, SPINNER_FRAME_INTERVAL_MS);
92
+ return () => clearInterval(id);
93
+ }, [animate, frames.length]);
94
+ return /* @__PURE__ */ jsx(Text, { color: terminalTheme.brand, children: frames[frame] });
95
+ };
96
+
97
+ // src/ui/components/Loader.tsx
98
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
64
99
  var LOADER_FRAME_INTERVAL_MS = 1e3;
65
100
  var Loader = ({
66
101
  step,
@@ -68,7 +103,7 @@ var Loader = ({
68
103
  animate = true
69
104
  }) => {
70
105
  const [frame, setFrame] = useState(0);
71
- const frames = asciiFrames;
106
+ const frames = getSpinnerFrames("line");
72
107
  useEffect(() => {
73
108
  if (!animate) return;
74
109
  const id = setInterval(() => {
@@ -76,13 +111,16 @@ var Loader = ({
76
111
  }, LOADER_FRAME_INTERVAL_MS);
77
112
  return () => clearInterval(id);
78
113
  }, [animate, frames.length]);
79
- const spinner = animate ? frames[frame] : "[..]";
80
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 1, children: steps.map((label, idx) => {
114
+ const activeFrame = animate ? frame : 0;
115
+ return /* @__PURE__ */ jsx2(Box, { flexDirection: "column", gap: 1, children: steps.map((label, idx) => {
81
116
  const isActive = idx === step;
82
117
  const isComplete = idx < step;
83
- const prefix = isComplete ? "[ok]" : isActive ? spinner : "[ ]";
118
+ const prefix = getLoaderStepMarker(
119
+ isComplete ? "complete" : isActive ? "active" : "pending",
120
+ activeFrame
121
+ );
84
122
  const color = isComplete ? terminalTheme.success : isActive ? terminalTheme.brand : void 0;
85
- return /* @__PURE__ */ jsxs(Text, { color, children: [
123
+ return /* @__PURE__ */ jsxs(Text2, { color, children: [
86
124
  prefix,
87
125
  " ",
88
126
  label
@@ -96,6 +134,162 @@ import { Box as Box3, Text as Text4 } from "ink";
96
134
  import { supportsLanguage } from "cli-highlight";
97
135
  import SyntaxHighlight from "ink-syntax-highlight";
98
136
 
137
+ // src/ui/citationContent.ts
138
+ var CITATION_TAG_RE = /\[S([A-Za-z0-9_.:-]+)\]/g;
139
+ var CITATION_TAG_ALT_RE = /\[(tool_[A-Za-z0-9_.:-]+)\]/g;
140
+ var DEFAULT_MAX_INLINE_CITATIONS_PER_SOURCE = 3;
141
+ var normalizeCitationSourceId = (sourceId) => sourceId.startsWith("_tool_") ? sourceId.slice(1) : sourceId;
142
+ var findCitationMatches = (value) => {
143
+ const matches = [];
144
+ let match;
145
+ CITATION_TAG_RE.lastIndex = 0;
146
+ while ((match = CITATION_TAG_RE.exec(value)) !== null) {
147
+ matches.push({
148
+ index: match.index,
149
+ end: match.index + match[0].length,
150
+ sourceId: normalizeCitationSourceId(match[1] ?? "")
151
+ });
152
+ }
153
+ CITATION_TAG_ALT_RE.lastIndex = 0;
154
+ while ((match = CITATION_TAG_ALT_RE.exec(value)) !== null) {
155
+ matches.push({
156
+ index: match.index,
157
+ end: match.index + match[0].length,
158
+ sourceId: normalizeCitationSourceId(match[1] ?? "")
159
+ });
160
+ }
161
+ matches.sort((a, b) => a.index - b.index);
162
+ const seenIndexes = /* @__PURE__ */ new Set();
163
+ return matches.filter((candidate) => {
164
+ if (seenIndexes.has(candidate.index)) {
165
+ return false;
166
+ }
167
+ seenIndexes.add(candidate.index);
168
+ return true;
169
+ });
170
+ };
171
+ var getCitationSourceOrder = (content) => {
172
+ const seen = /* @__PURE__ */ new Set();
173
+ const order = [];
174
+ for (const { sourceId } of findCitationMatches(content)) {
175
+ if (!sourceId || seen.has(sourceId)) {
176
+ continue;
177
+ }
178
+ seen.add(sourceId);
179
+ order.push(sourceId);
180
+ }
181
+ return order;
182
+ };
183
+ var citationNumberMap = (content) => {
184
+ const map = /* @__PURE__ */ new Map();
185
+ getCitationSourceOrder(content).forEach((sourceId, index) => {
186
+ map.set(sourceId, index + 1);
187
+ });
188
+ return map;
189
+ };
190
+ var toDisplayCitationContent = (content, options = {}) => {
191
+ if (!content) {
192
+ return content;
193
+ }
194
+ const matches = findCitationMatches(content);
195
+ if (!matches.length) {
196
+ return content;
197
+ }
198
+ const sourceIdToNumber = citationNumberMap(content);
199
+ const perSourceCount = /* @__PURE__ */ new Map();
200
+ const maxInlinePerSource = options.maxInlinePerSource ?? DEFAULT_MAX_INLINE_CITATIONS_PER_SOURCE;
201
+ let cursor = 0;
202
+ let result = "";
203
+ for (const match of matches) {
204
+ result += content.slice(cursor, match.index);
205
+ const count = (perSourceCount.get(match.sourceId) ?? 0) + 1;
206
+ perSourceCount.set(match.sourceId, count);
207
+ const number = sourceIdToNumber.get(match.sourceId);
208
+ if (number !== void 0 && count <= maxInlinePerSource) {
209
+ result += `[${number}]`;
210
+ }
211
+ cursor = match.end;
212
+ }
213
+ return result + content.slice(cursor);
214
+ };
215
+ var firstString = (...values) => {
216
+ for (const value of values) {
217
+ if (typeof value !== "string") {
218
+ continue;
219
+ }
220
+ const trimmed = value.trim();
221
+ if (trimmed) {
222
+ return trimmed;
223
+ }
224
+ }
225
+ return void 0;
226
+ };
227
+ var fallbackSourceLabel = (sourceId) => sourceId.replace(/^tool_/, "").replace(/[_:-]+/g, " ").trim() || sourceId;
228
+ var buildCitationReferences = ({
229
+ content,
230
+ toolsUsed,
231
+ citations
232
+ }) => {
233
+ const order = getCitationSourceOrder(content);
234
+ if (!order.length) {
235
+ return [];
236
+ }
237
+ const toolBySourceId = /* @__PURE__ */ new Map();
238
+ for (const tool of toolsUsed ?? []) {
239
+ const sourceId = typeof tool.source_id === "string" ? normalizeCitationSourceId(tool.source_id.trim()) : "";
240
+ if (sourceId && !toolBySourceId.has(sourceId)) {
241
+ toolBySourceId.set(sourceId, tool);
242
+ }
243
+ }
244
+ const citationBySourceId = /* @__PURE__ */ new Map();
245
+ for (const citation of citations ?? []) {
246
+ const sourceId = typeof citation.source_id === "string" ? normalizeCitationSourceId(citation.source_id.trim()) : "";
247
+ if (sourceId && !citationBySourceId.has(sourceId)) {
248
+ citationBySourceId.set(sourceId, citation);
249
+ }
250
+ }
251
+ return order.map((sourceId, index) => {
252
+ const tool = toolBySourceId.get(sourceId);
253
+ const citation = citationBySourceId.get(sourceId);
254
+ return {
255
+ number: index + 1,
256
+ sourceId,
257
+ label: firstString(
258
+ citation?.title,
259
+ tool?.title,
260
+ tool?.tool_friendly_name,
261
+ tool?.tool_name
262
+ ) ?? fallbackSourceLabel(sourceId),
263
+ url: firstString(citation?.url, tool?.source_url)
264
+ };
265
+ });
266
+ };
267
+ var buildReferencesSection = (references) => {
268
+ if (!references.length) {
269
+ return "";
270
+ }
271
+ return [
272
+ "---",
273
+ "## References",
274
+ ...references.map(
275
+ (reference) => `- [${reference.number}] ${reference.label}${reference.url ? ` - ${reference.url}` : ""}`
276
+ )
277
+ ].join("\n");
278
+ };
279
+ var toCitationExportContent = ({
280
+ content,
281
+ toolsUsed,
282
+ citations
283
+ }) => {
284
+ const displayContent = toDisplayCitationContent(content);
285
+ const references = buildReferencesSection(
286
+ buildCitationReferences({ content, toolsUsed, citations })
287
+ );
288
+ return references ? `${displayContent.trim()}
289
+
290
+ ${references}` : displayContent;
291
+ };
292
+
99
293
  // src/ui/transcriptModel.ts
100
294
  var hasRenderableTranscriptMessage = (message) => {
101
295
  const content = (message.content ?? "").trim();
@@ -105,31 +299,6 @@ var hasRenderableTranscriptMessages = (messages, excludeStreaming = false) => me
105
299
  (message) => excludeStreaming && message.role === "assistant" && message.pending ? false : hasRenderableTranscriptMessage(message)
106
300
  );
107
301
 
108
- // src/ui/components/Spinner.tsx
109
- import React2 from "react";
110
- import { Text as Text2 } from "ink";
111
- import { jsx as jsx2 } from "react/jsx-runtime";
112
- var dotFrames = ["\xB7 ", "\xB7\xB7 ", "\xB7\xB7\xB7"];
113
- var lineFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
114
- var pulseFrames = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
115
- var SPINNER_FRAME_INTERVAL_MS = 1e3;
116
- var shouldAnimateSpinner = (animate = true) => animate;
117
- var Spinner = ({ type = "dots", animate = true }) => {
118
- const [frame, setFrame] = React2.useState(0);
119
- const frames = type === "dots" ? dotFrames : type === "pulse" ? pulseFrames : lineFrames;
120
- React2.useEffect(() => {
121
- if (!shouldAnimateSpinner(animate)) {
122
- setFrame(0);
123
- return;
124
- }
125
- const id = setInterval(() => {
126
- setFrame((f) => (f + 1) % frames.length);
127
- }, SPINNER_FRAME_INTERVAL_MS);
128
- return () => clearInterval(id);
129
- }, [animate, frames.length]);
130
- return /* @__PURE__ */ jsx2(Text2, { children: frames[frame] });
131
- };
132
-
133
302
  // src/ui/components/TitledBox.tsx
134
303
  import { Box as Box2, Text as Text3 } from "ink";
135
304
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -197,6 +366,7 @@ var TitledBox = ({
197
366
  // src/ui/components/Transcript.tsx
198
367
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
199
368
  var AI_NAME = "Cloudeval AI";
369
+ var getTranscriptRoleColor = (role) => role === "user" ? terminalTheme.userName : terminalTheme.aiName;
200
370
  var normalizeFenceLanguage = (language) => {
201
371
  const normalized = (language ?? "").replace(/^```/, "").trim().split(/\s+/)[0]?.toLowerCase();
202
372
  return normalized || "text";
@@ -227,17 +397,35 @@ var parseMarkdown = (text) => {
227
397
  }
228
398
  return blocks;
229
399
  };
400
+ var tokenizeInlineMarkdown = (text) => {
401
+ const segments = text.split(/(`[^`\n]+`|\*\*[^*\n]+\*\*|\[[1-9][0-9]*\])/g).filter(Boolean);
402
+ return segments.map((segment) => {
403
+ if (segment.startsWith("`") && segment.endsWith("`")) {
404
+ return { type: "code", text: segment.slice(1, -1) };
405
+ }
406
+ if (segment.startsWith("**") && segment.endsWith("**")) {
407
+ return { type: "bold", text: segment.slice(2, -2) };
408
+ }
409
+ if (/^\[[1-9][0-9]*\]$/.test(segment)) {
410
+ return { type: "citation", text: segment };
411
+ }
412
+ return { type: "text", text: segment };
413
+ });
414
+ };
230
415
  var renderInlineMarkdown = (text, keyPrefix) => {
231
- const segments = text.split(/(`[^`\n]+`|\*\*[^*\n]+\*\*)/g).filter(Boolean);
416
+ const segments = tokenizeInlineMarkdown(text);
232
417
  return segments.map((segment, index) => {
233
418
  const key = `${keyPrefix}-${index}`;
234
- if (segment.startsWith("`") && segment.endsWith("`")) {
235
- return /* @__PURE__ */ jsx4(Text4, { color: terminalTheme.accent, children: segment.slice(1, -1) }, key);
419
+ if (segment.type === "code") {
420
+ return /* @__PURE__ */ jsx4(Text4, { color: terminalTheme.accent, children: segment.text }, key);
236
421
  }
237
- if (segment.startsWith("**") && segment.endsWith("**")) {
238
- return /* @__PURE__ */ jsx4(Text4, { bold: true, children: segment.slice(2, -2) }, key);
422
+ if (segment.type === "bold") {
423
+ return /* @__PURE__ */ jsx4(Text4, { bold: true, children: segment.text }, key);
239
424
  }
240
- return /* @__PURE__ */ jsx4(Text4, { children: segment }, key);
425
+ if (segment.type === "citation") {
426
+ return /* @__PURE__ */ jsx4(Text4, { color: terminalTheme.citation, bold: true, children: segment.text }, key);
427
+ }
428
+ return /* @__PURE__ */ jsx4(Text4, { children: segment.text }, key);
241
429
  });
242
430
  };
243
431
  var MarkdownText = ({
@@ -266,31 +454,66 @@ var MarkdownText = ({
266
454
  return /* @__PURE__ */ jsx4(Text4, { dimColor: dim, wrap: "wrap", children: renderInlineMarkdown(line, key) }, key);
267
455
  }) });
268
456
  };
269
- var FormattedContent = ({ content, role }) => {
457
+ var CitationReferences = ({
458
+ references
459
+ }) => {
460
+ if (!references.length) {
461
+ return null;
462
+ }
463
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
464
+ /* @__PURE__ */ jsx4(Text4, { color: terminalTheme.brand, children: "Sources" }),
465
+ references.map((reference) => /* @__PURE__ */ jsxs3(Text4, { dimColor: true, wrap: "wrap", children: [
466
+ /* @__PURE__ */ jsxs3(Text4, { color: terminalTheme.brand, children: [
467
+ "[",
468
+ reference.number,
469
+ "]"
470
+ ] }),
471
+ " ",
472
+ reference.label,
473
+ reference.url ? ` - ${reference.url}` : ""
474
+ ] }, reference.sourceId))
475
+ ] });
476
+ };
477
+ var FormattedContent = ({ message, role }) => {
478
+ const content = message.content;
270
479
  if (role === "user") {
271
480
  return /* @__PURE__ */ jsx4(MarkdownText, { content });
272
481
  }
273
482
  const blocks = parseMarkdown(content);
274
- return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: blocks.map((block, idx) => {
275
- if (block.type === "code") {
276
- const syntaxLanguage = getSyntaxHighlightLanguage(block.language);
277
- const blockTitle = block.language ? normalizeFenceLanguage(block.language) : "Code";
483
+ const references = buildCitationReferences({
484
+ content,
485
+ toolsUsed: message.toolsUsed,
486
+ citations: message.citations
487
+ });
488
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
489
+ blocks.map((block, idx) => {
490
+ if (block.type === "code") {
491
+ const syntaxLanguage = getSyntaxHighlightLanguage(block.language);
492
+ const blockTitle = block.language ? normalizeFenceLanguage(block.language) : "Code";
493
+ return /* @__PURE__ */ jsx4(
494
+ TitledBox,
495
+ {
496
+ title: blockTitle,
497
+ borderStyle: "single",
498
+ borderColor: terminalTheme.muted,
499
+ padding: 0,
500
+ paddingX: 1,
501
+ marginY: 1,
502
+ children: /* @__PURE__ */ jsx4(SyntaxHighlight, { code: block.content, language: syntaxLanguage })
503
+ },
504
+ idx
505
+ );
506
+ }
278
507
  return /* @__PURE__ */ jsx4(
279
- TitledBox,
508
+ MarkdownText,
280
509
  {
281
- title: blockTitle,
282
- borderStyle: "single",
283
- borderColor: terminalTheme.muted,
284
- padding: 0,
285
- paddingX: 1,
286
- marginY: 1,
287
- children: /* @__PURE__ */ jsx4(SyntaxHighlight, { code: block.content, language: syntaxLanguage })
510
+ content: toDisplayCitationContent(block.content)
288
511
  },
289
512
  idx
290
513
  );
291
- }
292
- return /* @__PURE__ */ jsx4(MarkdownText, { content: block.content }, idx);
293
- }) });
514
+ }),
515
+ /* @__PURE__ */ jsx4(CitationReferences, { references })
516
+ ] });
294
517
  };
295
518
  var formatDuration = (durationMs) => {
296
519
  if (durationMs < 1e3) {
@@ -329,6 +552,24 @@ var getStepStatusMeta = (status) => {
329
552
  }
330
553
  return { marker: "\u2022", label: status || "pending", color: terminalTheme.muted };
331
554
  };
555
+ var summarizeThinkingLedger = (steps) => {
556
+ const completed = steps.filter((step) => step.status === "completed").length;
557
+ const failed = steps.filter(
558
+ (step) => step.status === "error" || step.status === "aborted" || step.status === "cancelled"
559
+ ).length;
560
+ const running = steps.filter((step) => step.status === "streaming").length;
561
+ return {
562
+ title: "Task Ledger",
563
+ completed,
564
+ failed,
565
+ running,
566
+ parts: [
567
+ `${completed}/${steps.length} done`,
568
+ failed ? `${failed} failed` : "",
569
+ running ? `${running} running` : ""
570
+ ].filter(Boolean)
571
+ };
572
+ };
332
573
  var ProgressBar = ({
333
574
  completed,
334
575
  total,
@@ -347,7 +588,7 @@ var ProgressBar = ({
347
588
  (_, index) => active && index === pulsePosition ? "\u2501" : "\u2500"
348
589
  );
349
590
  return /* @__PURE__ */ jsxs3(Text4, { children: [
350
- /* @__PURE__ */ jsx4(Text4, { color: terminalTheme.success, children: "\u2501".repeat(filled) }),
591
+ /* @__PURE__ */ jsx4(Text4, { color: terminalTheme.brand, children: "\u2501".repeat(filled) }),
351
592
  /* @__PURE__ */ jsx4(Text4, { color: terminalTheme.danger, children: "\u2501".repeat(failedWidth) }),
352
593
  openRail.map((character, index) => /* @__PURE__ */ jsx4(
353
594
  Text4,
@@ -374,37 +615,29 @@ var ThinkingSteps = ({ message, expanded, forceExpanded = false, animate = true
374
615
  if (!steps.length) {
375
616
  return null;
376
617
  }
377
- const completedCount = steps.filter((step) => step.status === "completed").length;
378
- const failedCount = steps.filter(
379
- (step) => step.status === "error" || step.status === "aborted" || step.status === "cancelled"
380
- ).length;
381
- const runningCount = steps.filter((step) => step.status === "streaming").length;
382
- const summaryParts = [
383
- `${completedCount}/${steps.length} completed`,
384
- failedCount ? `${failedCount} failed` : "",
385
- runningCount ? `${runningCount} running` : ""
386
- ].filter(Boolean);
618
+ const ledger = summarizeThinkingLedger(steps);
387
619
  const runningStep = [...steps].reverse().find((step) => step.status === "streaming");
388
620
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
389
621
  /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", gap: 1, children: [
390
- /* @__PURE__ */ jsxs3(Text4, { color: message.pending ? terminalTheme.brand : terminalTheme.muted, children: [
622
+ /* @__PURE__ */ jsxs3(Text4, { color: terminalTheme.brand, children: [
391
623
  isExpanded ? "\u25BE" : "\u25B8",
392
- " Reasoning"
624
+ " ",
625
+ ledger.title
393
626
  ] }),
394
627
  message.pending ? /* @__PURE__ */ jsx4(Spinner, { type: "pulse", animate }) : null,
395
628
  /* @__PURE__ */ jsx4(
396
629
  ProgressBar,
397
630
  {
398
- completed: completedCount,
399
- failed: failedCount,
631
+ completed: ledger.completed,
632
+ failed: ledger.failed,
400
633
  total: steps.length,
401
- active: message.pending || runningCount > 0,
634
+ active: message.pending || ledger.running > 0,
402
635
  pulseIndex: animate ? Math.floor(now / 1e3) : 0
403
636
  }
404
637
  ),
405
638
  /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
406
639
  "(",
407
- summaryParts.join(", "),
640
+ ledger.parts.join(", "),
408
641
  ")"
409
642
  ] })
410
643
  ] }),
@@ -456,7 +689,7 @@ var Transcript = ({
456
689
  const hasThinkingSteps = Boolean(message.thinkingSteps?.length);
457
690
  if (!content && !hasThinkingSteps) return null;
458
691
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingY: 0, marginBottom: 1, children: [
459
- /* @__PURE__ */ jsxs3(Text4, { bold: true, color: isUser ? terminalTheme.success : terminalTheme.brand, children: [
692
+ /* @__PURE__ */ jsxs3(Text4, { bold: true, color: getTranscriptRoleColor(isUser ? "user" : "assistant"), children: [
460
693
  isUser ? userName : AI_NAME,
461
694
  ":",
462
695
  isUser && message.queued ? " (queued)" : ""
@@ -469,7 +702,7 @@ var Transcript = ({
469
702
  animate
470
703
  }
471
704
  ) : null,
472
- /* @__PURE__ */ jsx4(Box3, { paddingLeft: 0, children: content ? /* @__PURE__ */ jsx4(FormattedContent, { content, role: message.role }) : !hasThinkingSteps && !message.error ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No final response content." }) : null }),
705
+ /* @__PURE__ */ jsx4(Box3, { paddingLeft: 0, children: content ? /* @__PURE__ */ jsx4(FormattedContent, { message, role: message.role }) : !hasThinkingSteps && !message.error ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No final response content." }) : null }),
473
706
  !isUser && message.followUpQuestions?.length ? /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, italic: true, children: "Follow-ups are available above the input." }) }) : null,
474
707
  !isUser && message.hitlQuestionsAnswered?.answers.length ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: [
475
708
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, italic: true, children: "Human input:" }),
@@ -492,7 +725,7 @@ var Transcript = ({
492
725
  const hasThinkingSteps = Boolean(streamingMessage.thinkingSteps?.length);
493
726
  if (!hasContent && hasThinkingSteps) {
494
727
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingY: 0, children: [
495
- /* @__PURE__ */ jsxs3(Text4, { bold: true, color: terminalTheme.brand, children: [
728
+ /* @__PURE__ */ jsxs3(Text4, { bold: true, color: getTranscriptRoleColor("assistant"), children: [
496
729
  AI_NAME,
497
730
  ":"
498
731
  ] }),
@@ -509,7 +742,7 @@ var Transcript = ({
509
742
  }
510
743
  if (hasContent) {
511
744
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingY: 0, children: [
512
- /* @__PURE__ */ jsxs3(Text4, { bold: true, color: terminalTheme.brand, children: [
745
+ /* @__PURE__ */ jsxs3(Text4, { bold: true, color: getTranscriptRoleColor("assistant"), children: [
513
746
  AI_NAME,
514
747
  ":"
515
748
  ] }),
@@ -523,7 +756,7 @@ var Transcript = ({
523
756
  }
524
757
  ),
525
758
  /* @__PURE__ */ jsxs3(Box3, { paddingLeft: 0, children: [
526
- /* @__PURE__ */ jsx4(FormattedContent, { content, role: "assistant" }),
759
+ /* @__PURE__ */ jsx4(FormattedContent, { message: streamingMessage, role: "assistant" }),
527
760
  /* @__PURE__ */ jsx4(Text4, { color: terminalTheme.cursor, children: "|" })
528
761
  ] })
529
762
  ] }, streamingMessage.id);
@@ -602,7 +835,7 @@ var getTuiKeyBindings = (platform = process.platform) => ({
602
835
  quit: "Ctrl+C quit",
603
836
  tabFocus: "Tab focus",
604
837
  tabSwitch: "Arrow keys move focus",
605
- commandComplete: "Tab completes slash commands",
838
+ commandComplete: "Tab/\u2191\u2193 slash command menu",
606
839
  historySearch: "Ctrl+R history search",
607
840
  cancel: "Esc cancel response",
608
841
  scroll: "Ctrl+Up/Down scroll",
@@ -622,9 +855,9 @@ var getChatInputHelpText = ({
622
855
  return "Type to edit | Tab/Arrows controls | 1-8 tabs | /help";
623
856
  }
624
857
  if (promptCount <= 0) {
625
- return "Enter send | Esc controls | Ctrl+J newline | /starter | /help";
858
+ return "Enter send | Esc controls | /copy | /download | /help";
626
859
  }
627
- return "Enter send/choose | Esc controls | Tab focus | 1-8 tabs | /help";
860
+ return "Enter send/choose | Esc controls | /copy | Tab focus | /help";
628
861
  };
629
862
 
630
863
  // src/ui/workspaceTabs.ts
@@ -666,6 +899,16 @@ var workspaceTabFromPromptChange = (previousValue, nextValue) => {
666
899
  return nextValue.length === 1 ? workspaceTabFromShortcut(nextValue) : void 0;
667
900
  };
668
901
  var workspaceTabButtonLabel = (tab) => `${workspaceTabs.indexOf(tab) + 1} ${workspaceTabLabels[tab]}`;
902
+ var workspaceTabButtonContent = (tab, active = false) => `${active ? raisedButtonStyle.activeMarker : raisedButtonStyle.inactiveMarker} ${workspaceTabButtonLabel(tab)}`;
903
+ var workspaceTabButtonInterior = (tab, active = false) => ` ${workspaceTabButtonContent(tab, active)} `;
904
+ var workspaceTabButtonStyle = (active = false) => ({
905
+ backgroundColor: active ? terminalTheme.selectedBackground : void 0,
906
+ borderColor: active ? terminalTheme.focus : terminalTheme.muted,
907
+ borderStyle: active ? "bold" : raisedButtonStyle.border,
908
+ color: active ? "black" : terminalTheme.muted,
909
+ bold: active,
910
+ underline: active
911
+ });
669
912
  var getWorkspaceTabHitAreas = ({
670
913
  startColumn = 1,
671
914
  startRow = 1,
@@ -712,7 +955,7 @@ var shouldBlinkPromptCursor = ({
712
955
  busy = false,
713
956
  selectorOpen = false,
714
957
  searching = false
715
- }) => Boolean(animationsEnabled && inputActive && busy && !selectorOpen && !searching);
958
+ }) => Boolean(animationsEnabled && inputActive && !selectorOpen && !searching);
716
959
  var getInputCursorColor = ({
717
960
  disabled,
718
961
  inputActive = true,
@@ -732,6 +975,20 @@ var getInputCursorGlyph = ({
732
975
  }
733
976
  return cursorVisible ? "\u258C" : "\u258F";
734
977
  };
978
+ var getPromptDisplayRows = ({
979
+ visibleRows,
980
+ startRow,
981
+ inputRowCount
982
+ }) => visibleRows.map((line, index) => {
983
+ const rowIndex = startRow + index;
984
+ const isFiller = rowIndex >= inputRowCount;
985
+ return {
986
+ line,
987
+ isFiller,
988
+ showPromptPrefix: !isFiller,
989
+ showCursor: !isFiller && rowIndex === inputRowCount - 1
990
+ };
991
+ });
735
992
  var Scrollbar = ({
736
993
  totalRows,
737
994
  visibleRows,
@@ -833,8 +1090,49 @@ var getFollowUpRowViewport = ({
833
1090
  rowCount: 1
834
1091
  };
835
1092
  };
1093
+ var getCommandCompletionViewport = ({
1094
+ commands,
1095
+ focusedIndex = 0,
1096
+ terminalColumns
1097
+ }) => {
1098
+ const availableWidth = Math.max(24, terminalColumns - 28);
1099
+ const activeIndex = commands.length ? Math.min(Math.max(0, focusedIndex), commands.length - 1) : 0;
1100
+ const items = commands.map((command, index) => {
1101
+ const label = `${index === activeIndex ? raisedButtonStyle.activeMarker : raisedButtonStyle.inactiveMarker} ${command.name}`;
1102
+ return { command, index, label, width: label.length };
1103
+ });
1104
+ if (!items.length) {
1105
+ return { items: [], clippedStart: false, clippedEnd: false, rowCount: 0 };
1106
+ }
1107
+ let start = activeIndex;
1108
+ let usedWidth = items[start]?.width ?? 0;
1109
+ while (start > 0) {
1110
+ const nextWidth = items[start - 1].width + 1;
1111
+ if (usedWidth + nextWidth > availableWidth) {
1112
+ break;
1113
+ }
1114
+ usedWidth += nextWidth;
1115
+ start--;
1116
+ }
1117
+ let end = activeIndex + 1;
1118
+ while (end < items.length) {
1119
+ const nextWidth = items[end].width + (end > start ? 1 : 0);
1120
+ if (usedWidth + nextWidth > availableWidth) {
1121
+ break;
1122
+ }
1123
+ usedWidth += nextWidth;
1124
+ end++;
1125
+ }
1126
+ return {
1127
+ items: items.slice(start, end),
1128
+ clippedStart: start > 0,
1129
+ clippedEnd: end < items.length,
1130
+ rowCount: 1
1131
+ };
1132
+ };
836
1133
  var InputBox = ({
837
1134
  title = "Prompt",
1135
+ variant = "panel",
838
1136
  value,
839
1137
  onChange,
840
1138
  onSubmit,
@@ -857,7 +1155,11 @@ var InputBox = ({
857
1155
  scrollOffset,
858
1156
  onTabShortcut,
859
1157
  blinkCursor = false,
860
- ghostText
1158
+ ghostText,
1159
+ commandCompletions = [],
1160
+ focusedCommandCompletionIndex,
1161
+ commandCompletionsActive = false,
1162
+ onCommandCompletionSubmit
861
1163
  }) => {
862
1164
  const [cursorVisible, setCursorVisible] = useState2(true);
863
1165
  const keyBindings = getTuiKeyBindings();
@@ -869,7 +1171,22 @@ var InputBox = ({
869
1171
  focusedFollowUpIndex,
870
1172
  terminalColumns
871
1173
  });
872
- const inputWidth = Math.max(20, terminalColumns - 8);
1174
+ const commandCompletionViewport = getCommandCompletionViewport({
1175
+ commands: commandCompletions,
1176
+ focusedIndex: focusedCommandCompletionIndex,
1177
+ terminalColumns
1178
+ });
1179
+ const commandCompletionLine = [
1180
+ commandCompletionViewport.clippedStart ? "<--" : void 0,
1181
+ ...commandCompletionViewport.items.map((item) => item.label),
1182
+ commandCompletionViewport.clippedEnd ? "-->" : void 0
1183
+ ].filter(Boolean).join(" ");
1184
+ const activeCommand = focusedCommandCompletionIndex === void 0 ? void 0 : commandCompletions[Math.min(
1185
+ Math.max(0, focusedCommandCompletionIndex),
1186
+ Math.max(0, commandCompletions.length - 1)
1187
+ )];
1188
+ const promptPrefix = ">";
1189
+ const inputWidth = Math.max(20, terminalColumns - 10);
873
1190
  const inputViewport = getInputViewport({
874
1191
  value,
875
1192
  width: inputWidth,
@@ -881,6 +1198,11 @@ var InputBox = ({
881
1198
  const visibleRowCount = inputViewport.visibleRowCount;
882
1199
  const startRow = inputViewport.startRow;
883
1200
  const visibleRows = inputViewport.visibleRows;
1201
+ const promptRows = getPromptDisplayRows({
1202
+ visibleRows,
1203
+ startRow,
1204
+ inputRowCount: inputRows.length
1205
+ });
884
1206
  useEffect2(() => {
885
1207
  if (!shouldAnimateInputCursor({ disabled, inputActive, blinkCursor })) {
886
1208
  setCursorVisible(true);
@@ -920,6 +1242,10 @@ var InputBox = ({
920
1242
  insertNewline();
921
1243
  return;
922
1244
  }
1245
+ if (commandCompletionsActive && activeCommand && onCommandCompletionSubmit) {
1246
+ onCommandCompletionSubmit(activeCommand);
1247
+ return;
1248
+ }
923
1249
  if (!shouldSubmitInputOnReturn(value)) {
924
1250
  return;
925
1251
  }
@@ -952,7 +1278,98 @@ var InputBox = ({
952
1278
  const cursorGlyph = getInputCursorGlyph({ inputActive, cursorVisible });
953
1279
  const cursorColor = getInputCursorColor({ disabled, inputActive, cursorVisible });
954
1280
  const inputBorderColor = disabled ? terminalTheme.muted : followUpsActive || inputActive ? terminalTheme.focus : terminalTheme.muted;
955
- return /* @__PURE__ */ jsxs4(
1281
+ const content = /* @__PURE__ */ jsxs4(Fragment, { children: [
1282
+ followUps.length ? /* @__PURE__ */ jsxs4(Text5, { wrap: "truncate", children: [
1283
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `${followUpsLabel}: ` }),
1284
+ followUpViewport.clippedStart ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "<-- " }) : null,
1285
+ followUpViewport.items.map(({ question, index, label }) => {
1286
+ const focused = followUpsActive && focusedFollowUpIndex === index;
1287
+ return /* @__PURE__ */ jsxs4(
1288
+ Text5,
1289
+ {
1290
+ color: focused ? terminalTheme.focus : void 0,
1291
+ bold: focused,
1292
+ children: [
1293
+ focused ? raisedButtonStyle.activeMarker : raisedButtonStyle.inactiveMarker,
1294
+ " ",
1295
+ label,
1296
+ " "
1297
+ ]
1298
+ },
1299
+ `${index}-${question}`
1300
+ );
1301
+ }),
1302
+ followUpViewport.clippedEnd ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "-->" }) : null
1303
+ ] }) : null,
1304
+ /* @__PURE__ */ jsxs4(
1305
+ Box4,
1306
+ {
1307
+ flexDirection: compact ? "column" : "row",
1308
+ children: [
1309
+ /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", flexGrow: 1, children: !value ? /* @__PURE__ */ jsxs4(Text5, { wrap: "truncate", children: [
1310
+ /* @__PURE__ */ jsxs4(Text5, { color: inputActive ? terminalTheme.brand : terminalTheme.muted, children: [
1311
+ promptPrefix,
1312
+ " "
1313
+ ] }),
1314
+ /* @__PURE__ */ jsx5(Text5, { color: cursorColor, children: cursorGlyph }),
1315
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${placeholder}` })
1316
+ ] }) : promptRows.map((row, index) => {
1317
+ return /* @__PURE__ */ jsxs4(Text5, { wrap: "truncate", children: [
1318
+ row.showPromptPrefix ? /* @__PURE__ */ jsxs4(Text5, { color: inputActive ? terminalTheme.brand : terminalTheme.muted, children: [
1319
+ promptPrefix,
1320
+ " "
1321
+ ] }) : /* @__PURE__ */ jsx5(Text5, { children: " " }),
1322
+ row.line,
1323
+ row.showCursor ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1324
+ /* @__PURE__ */ jsx5(
1325
+ Text5,
1326
+ {
1327
+ dimColor: true,
1328
+ italic: true,
1329
+ color: terminalTheme.inputGhost,
1330
+ children: ghostText ?? ""
1331
+ }
1332
+ ),
1333
+ /* @__PURE__ */ jsx5(Text5, { color: cursorColor, children: cursorGlyph })
1334
+ ] }) : null
1335
+ ] }, `${startRow}-${index}`);
1336
+ }) }),
1337
+ /* @__PURE__ */ jsx5(
1338
+ Scrollbar,
1339
+ {
1340
+ totalRows: inputRows.length,
1341
+ visibleRows: visibleRowCount,
1342
+ startRow
1343
+ }
1344
+ )
1345
+ ]
1346
+ }
1347
+ ),
1348
+ actionHint ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "truncate", children: actionHint }) : resolvedHelpText ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "truncate", children: resolvedHelpText }) : null,
1349
+ commandCompletionViewport.items.length ? /* @__PURE__ */ jsx5(
1350
+ Text5,
1351
+ {
1352
+ color: commandCompletionsActive ? terminalTheme.focus : terminalTheme.muted,
1353
+ bold: commandCompletionsActive,
1354
+ wrap: "truncate",
1355
+ children: `/commands: ${commandCompletionLine} | Tab/\u2191\u2193 move | Enter choose`
1356
+ }
1357
+ ) : null,
1358
+ footerControls ? /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", marginTop: 1, children: footerControls }) : null
1359
+ ] });
1360
+ if (variant === "dock") {
1361
+ return /* @__PURE__ */ jsx5(
1362
+ Box4,
1363
+ {
1364
+ flexDirection: "column",
1365
+ borderStyle: "single",
1366
+ borderColor: inputBorderColor,
1367
+ paddingX: 1,
1368
+ children: content
1369
+ }
1370
+ );
1371
+ }
1372
+ return /* @__PURE__ */ jsx5(
956
1373
  TitledBox,
957
1374
  {
958
1375
  title,
@@ -960,69 +1377,7 @@ var InputBox = ({
960
1377
  borderColor: inputBorderColor,
961
1378
  padding: 0,
962
1379
  paddingX: 1,
963
- children: [
964
- followUps.length ? /* @__PURE__ */ jsxs4(Text5, { wrap: "truncate", children: [
965
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `${followUpsLabel}: ` }),
966
- followUpViewport.clippedStart ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "<-- " }) : null,
967
- followUpViewport.items.map(({ question, index, label }) => {
968
- const focused = followUpsActive && focusedFollowUpIndex === index;
969
- return /* @__PURE__ */ jsxs4(
970
- Text5,
971
- {
972
- color: focused ? terminalTheme.focus : void 0,
973
- bold: focused,
974
- children: [
975
- focused ? raisedButtonStyle.activeMarker : raisedButtonStyle.inactiveMarker,
976
- " ",
977
- label,
978
- " "
979
- ]
980
- },
981
- `${index}-${question}`
982
- );
983
- }),
984
- followUpViewport.clippedEnd ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "-->" }) : null
985
- ] }) : null,
986
- /* @__PURE__ */ jsxs4(
987
- Box4,
988
- {
989
- flexDirection: compact ? "column" : "row",
990
- children: [
991
- /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", flexGrow: 1, children: !value ? /* @__PURE__ */ jsxs4(Text5, { wrap: "truncate", children: [
992
- /* @__PURE__ */ jsx5(Text5, { color: cursorColor, children: cursorGlyph }),
993
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${placeholder}` })
994
- ] }) : visibleRows.map((line, index) => {
995
- const isLastValueRow = startRow + index === inputRows.length - 1;
996
- return /* @__PURE__ */ jsxs4(Text5, { wrap: "truncate", children: [
997
- line,
998
- isLastValueRow ? /* @__PURE__ */ jsxs4(Fragment, { children: [
999
- /* @__PURE__ */ jsx5(
1000
- Text5,
1001
- {
1002
- dimColor: true,
1003
- italic: true,
1004
- color: terminalTheme.inputGhost,
1005
- children: ghostText ?? ""
1006
- }
1007
- ),
1008
- /* @__PURE__ */ jsx5(Text5, { color: cursorColor, children: cursorGlyph })
1009
- ] }) : null
1010
- ] }, `${startRow}-${index}`);
1011
- }) }),
1012
- /* @__PURE__ */ jsx5(
1013
- Scrollbar,
1014
- {
1015
- totalRows: inputRows.length,
1016
- visibleRows: visibleRowCount,
1017
- startRow
1018
- }
1019
- )
1020
- ]
1021
- }
1022
- ),
1023
- actionHint ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "truncate", children: actionHint }) : resolvedHelpText ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "truncate", children: resolvedHelpText }) : null,
1024
- footerControls ? /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", marginTop: 1, children: footerControls }) : null
1025
- ]
1380
+ children: content
1026
1381
  }
1027
1382
  );
1028
1383
  };
@@ -1242,7 +1597,7 @@ var slashCommands = [
1242
1597
  {
1243
1598
  name: "/thread",
1244
1599
  aliases: ["/threads"],
1245
- description: "Open thread selector or use /thread <title-or-id>."
1600
+ description: "Open session/thread selector or use /thread <title-or-id>."
1246
1601
  },
1247
1602
  {
1248
1603
  name: "/project",
@@ -1284,6 +1639,16 @@ var slashCommands = [
1284
1639
  aliases: ["/frontend"],
1285
1640
  description: "Open the current thread in the frontend."
1286
1641
  },
1642
+ {
1643
+ name: "/copy",
1644
+ aliases: ["/copy-response"],
1645
+ description: "Copy the latest assistant response with numbered citations."
1646
+ },
1647
+ {
1648
+ name: "/download",
1649
+ aliases: ["/export"],
1650
+ description: "Download the current chat transcript as Markdown."
1651
+ },
1287
1652
  {
1288
1653
  name: "/help",
1289
1654
  aliases: ["/?"],
@@ -1300,6 +1665,28 @@ var firstTokenAndRest = (input) => {
1300
1665
  };
1301
1666
  };
1302
1667
  var allCommandNames = () => slashCommands.flatMap((command) => [command.name, ...command.aliases]);
1668
+ var buildSlashCommandCompletionItems = (input) => {
1669
+ const trimmed = input.trimStart();
1670
+ if (!trimmed.startsWith("/") || /\s/.test(trimmed)) {
1671
+ return [];
1672
+ }
1673
+ const query = normalize(trimmed);
1674
+ return slashCommands.filter((command) => {
1675
+ if (query === "/") {
1676
+ return true;
1677
+ }
1678
+ return [command.name, ...command.aliases].some(
1679
+ (name) => name.startsWith(query)
1680
+ );
1681
+ });
1682
+ };
1683
+ var slashCommandGhostSuffix = (input, command) => {
1684
+ const trimmed = input.trimStart();
1685
+ if (!command || !trimmed.startsWith("/") || /\s/.test(trimmed)) {
1686
+ return void 0;
1687
+ }
1688
+ return toGhostSuffix(trimmed, command.name);
1689
+ };
1303
1690
  var completeFromCandidates = (input, values, previous) => {
1304
1691
  const candidates = [...new Set(values)].filter(Boolean);
1305
1692
  if (!candidates.length) {
@@ -1314,12 +1701,12 @@ var completeFromCandidates = (input, values, previous) => {
1314
1701
  index
1315
1702
  };
1316
1703
  };
1317
- var toGhostSuffix = (input, suggestion) => {
1704
+ function toGhostSuffix(input, suggestion) {
1318
1705
  if (!suggestion.startsWith(input) || suggestion === input) {
1319
1706
  return void 0;
1320
1707
  }
1321
1708
  return suggestion.slice(input.length);
1322
- };
1709
+ }
1323
1710
  var modelValue = (item) => item.value || "auto";
1324
1711
  var projectName = (project) => project.name || project.id;
1325
1712
  var completePromptInput = (input, context, previous) => {
@@ -1484,6 +1871,12 @@ var resolvePromptCommand = (input, context) => {
1484
1871
  if (command === "/open" || command === "/frontend") {
1485
1872
  return { type: "openFrontend" };
1486
1873
  }
1874
+ if (command === "/copy" || command === "/copy-response") {
1875
+ return { type: "copyLatestResponse" };
1876
+ }
1877
+ if (command === "/download" || command === "/export") {
1878
+ return { type: "downloadTranscript" };
1879
+ }
1487
1880
  if (command === "/help" || command === "/?") {
1488
1881
  return { type: "showHelp" };
1489
1882
  }
@@ -1503,11 +1896,18 @@ var creditProgressText = ({
1503
1896
  const filled = Math.round(ratio * safeWidth);
1504
1897
  return `[${"\u2588".repeat(filled)}${"\u2591".repeat(safeWidth - filled)}] ${Math.round(ratio * 100)}%`;
1505
1898
  };
1506
- var billingSummaryText = (billing, progressWidth = 10) => billing ? `Plan: ${billing.plan} | Credits: ${formatCredits(billing.remaining)}/${formatCredits(billing.total)} ${creditProgressText({
1507
- remaining: billing.remaining,
1508
- total: billing.total,
1509
- width: progressWidth
1510
- })}` : "Plan: loading | Credits: loading";
1899
+ var billingSummaryText = (billing, progressWidth = 10) => {
1900
+ if (!billing) {
1901
+ return "Plan: loading | Credits: loading";
1902
+ }
1903
+ const used = Math.max(Number(billing.reportedUsed ?? billing.used ?? 0), 0);
1904
+ const creditLabel = used > 0 ? `${formatCredits(billing.remaining)} left | Used: ${formatCredits(used)}` : `${formatCredits(billing.remaining)}/${formatCredits(billing.total)}`;
1905
+ return `Plan: ${billing.plan} | Credits: ${creditLabel} ${creditProgressText({
1906
+ remaining: billing.remaining,
1907
+ total: billing.total,
1908
+ width: progressWidth
1909
+ })}`;
1910
+ };
1511
1911
 
1512
1912
  // src/ui/interactionModel.ts
1513
1913
  var selectorControls = ["thread", "project", "model", "mode", "profile"];
@@ -1692,10 +2092,40 @@ var getPromptInputRowBudget = (size) => {
1692
2092
  const rows = normalizeDimension(size.rows, 32);
1693
2093
  return clamp2(Math.floor(rows * 0.13), 4, 8);
1694
2094
  };
1695
- var shouldUseSplitChatLayout = (size) => {
2095
+ var getChatResponsiveMode = (size) => {
1696
2096
  const columns = normalizeDimension(size.columns, 100);
1697
2097
  const rows = normalizeDimension(size.rows, 32);
1698
- return columns >= 132 && rows >= 40;
2098
+ if (columns >= 132 && rows >= 40) {
2099
+ return "wide";
2100
+ }
2101
+ if (columns >= 112 && rows >= 34) {
2102
+ return "medium";
2103
+ }
2104
+ return "narrow";
2105
+ };
2106
+ var shouldUseSplitChatLayout = (size) => getChatResponsiveMode(size) !== "narrow";
2107
+ var getContextRailWidth = (size) => {
2108
+ const mode = getChatResponsiveMode(size);
2109
+ const columns = normalizeDimension(size.columns, 100);
2110
+ if (mode === "wide") {
2111
+ return Math.min(40, Math.max(34, Math.ceil(columns * 0.25)));
2112
+ }
2113
+ if (mode === "medium") {
2114
+ return Math.min(30, Math.max(28, Math.ceil(columns * 0.25)));
2115
+ }
2116
+ return 0;
2117
+ };
2118
+ var estimateComposerRows = ({
2119
+ inputRows,
2120
+ suggestionRows,
2121
+ includeControls = true,
2122
+ controlRows = 0,
2123
+ variant = "panel"
2124
+ }) => {
2125
+ const chromeRows = variant === "dock" ? 1 : 2;
2126
+ const inputRowsWithHint = inputRows + 1;
2127
+ const footerRows = includeControls ? controlRows : 0;
2128
+ return chromeRows + Math.max(0, suggestionRows) + inputRowsWithHint + footerRows;
1699
2129
  };
1700
2130
  var truncateForTerminal = (value, maxLength) => {
1701
2131
  if (maxLength <= 0) {
@@ -1710,6 +2140,130 @@ var truncateForTerminal = (value, maxLength) => {
1710
2140
  return `${value.slice(0, maxLength - 3)}...`;
1711
2141
  };
1712
2142
 
2143
+ // src/ui/artifactChips.ts
2144
+ var buildChatArtifactChips = ({
2145
+ projectName: projectName2,
2146
+ reportsStatus = "idle",
2147
+ coverageLabel,
2148
+ topActionCount = 0,
2149
+ frontendThreadUrl
2150
+ }) => {
2151
+ const chips = [];
2152
+ if (projectName2) {
2153
+ chips.push({ label: "Project", value: projectName2, tone: "brand" });
2154
+ }
2155
+ if (coverageLabel) {
2156
+ chips.push({ label: "Reports", value: coverageLabel, tone: "success" });
2157
+ } else if (reportsStatus === "loading" || reportsStatus === "ready" || reportsStatus === "error") {
2158
+ chips.push({
2159
+ label: "Reports",
2160
+ value: reportsStatus,
2161
+ tone: reportsStatus === "error" ? "danger" : "normal"
2162
+ });
2163
+ }
2164
+ if (topActionCount > 0) {
2165
+ chips.push({
2166
+ label: "Actions",
2167
+ value: `${topActionCount} next`,
2168
+ tone: "warning"
2169
+ });
2170
+ }
2171
+ if (frontendThreadUrl) {
2172
+ chips.push({ label: "Web", value: "open thread", tone: "normal" });
2173
+ }
2174
+ return chips;
2175
+ };
2176
+
2177
+ // src/ui/chatResponseActions.ts
2178
+ import { spawnSync } from "child_process";
2179
+ import { mkdirSync, writeFileSync } from "fs";
2180
+ import { join, resolve } from "path";
2181
+ var getLatestAssistantMessage = (messages) => [...messages].reverse().find((message) => message.role === "assistant" && message.content.trim());
2182
+ var getClipboardCommand = (platform = process.platform) => {
2183
+ if (platform === "darwin") {
2184
+ return { command: "pbcopy", args: [] };
2185
+ }
2186
+ if (platform === "win32") {
2187
+ return { command: "cmd", args: ["/c", "clip"] };
2188
+ }
2189
+ return {
2190
+ command: "sh",
2191
+ args: [
2192
+ "-lc",
2193
+ "if command -v wl-copy >/dev/null 2>&1; then wl-copy; elif command -v xclip >/dev/null 2>&1; then xclip -selection clipboard; elif command -v xsel >/dev/null 2>&1; then xsel --clipboard --input; else exit 127; fi"
2194
+ ]
2195
+ };
2196
+ };
2197
+ var copyTextToClipboard = (text, options = {}) => {
2198
+ const clipboardCommand = getClipboardCommand(options.platform);
2199
+ const result = (options.spawnSyncImpl ?? spawnSync)(
2200
+ clipboardCommand.command,
2201
+ clipboardCommand.args,
2202
+ { input: text, encoding: "utf8" }
2203
+ );
2204
+ if (result.error) {
2205
+ throw result.error;
2206
+ }
2207
+ if (result.status !== 0) {
2208
+ throw new Error("No clipboard command is available for this terminal.");
2209
+ }
2210
+ };
2211
+ var buildLatestAssistantResponseText = (messages) => {
2212
+ const message = getLatestAssistantMessage(messages);
2213
+ if (!message) {
2214
+ return void 0;
2215
+ }
2216
+ return toCitationExportContent({
2217
+ content: message.content,
2218
+ toolsUsed: message.toolsUsed,
2219
+ citations: message.citations
2220
+ }).trim();
2221
+ };
2222
+ var roleLabel = (message, userName) => message.role === "user" ? userName : "Cloudeval AI";
2223
+ var buildChatTranscriptMarkdown = ({
2224
+ messages,
2225
+ userName,
2226
+ threadId,
2227
+ exportedAt = /* @__PURE__ */ new Date()
2228
+ }) => {
2229
+ const renderedMessages = messages.filter((message) => message.content.trim()).map((message) => {
2230
+ const content = message.role === "assistant" ? toCitationExportContent({
2231
+ content: message.content,
2232
+ toolsUsed: message.toolsUsed,
2233
+ citations: message.citations
2234
+ }) : message.content;
2235
+ return `## ${roleLabel(message, userName)}
2236
+
2237
+ ${content.trim()}`;
2238
+ });
2239
+ return [
2240
+ "# CloudEval chat transcript",
2241
+ "",
2242
+ `Exported: ${exportedAt.toISOString()}`,
2243
+ threadId ? `Thread: ${threadId}` : void 0,
2244
+ "",
2245
+ ...renderedMessages,
2246
+ ""
2247
+ ].filter((line) => line !== void 0).join("\n");
2248
+ };
2249
+ var writeChatTranscriptDownload = ({
2250
+ messages,
2251
+ userName,
2252
+ threadId,
2253
+ cwd = process.cwd()
2254
+ }) => {
2255
+ const dir = resolve(cwd, ".cloudeval-downloads");
2256
+ mkdirSync(dir, { recursive: true });
2257
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2258
+ const file = join(dir, `chat-transcript-${timestamp}.md`);
2259
+ writeFileSync(
2260
+ file,
2261
+ buildChatTranscriptMarkdown({ messages, userName, threadId }),
2262
+ "utf8"
2263
+ );
2264
+ return file;
2265
+ };
2266
+
1713
2267
  // src/ui/scrollBehavior.ts
1714
2268
  var shouldAutoScrollToBottom = ({
1715
2269
  currentOffset,
@@ -2042,6 +2596,11 @@ var sessionDescription = (session, age) => {
2042
2596
  const messages = `${session.messageCount} message${session.messageCount === 1 ? "" : "s"}`;
2043
2597
  return [createdAgeDescription(age), project, messages].filter(Boolean).join(" \xB7 ");
2044
2598
  };
2599
+ var draftDescription = (draft, age) => {
2600
+ const messages = `${draft.messageCount} message${draft.messageCount === 1 ? "" : "s"}`;
2601
+ const status = draft.status && draft.status !== "idle" && draft.status !== "complete" ? draft.status.replace(/_/g, " ") : "active locally";
2602
+ return ["Open session", status, createdAgeDescription(age), draft.projectName, messages].filter(Boolean).join(" \xB7 ");
2603
+ };
2045
2604
  var remoteTitle = (thread) => {
2046
2605
  const title = String(thread.title || "").trim();
2047
2606
  const preview = String(thread.last_message_preview || "").trim();
@@ -2133,12 +2692,48 @@ var localSessionMessagesToChatMessages = (session) => session.messages.map((mess
2133
2692
  content: message.content,
2134
2693
  createdAt: timestampFromIso(message.createdAt, index)
2135
2694
  }));
2695
+ var buildDraftThreadSummary = ({
2696
+ key,
2697
+ state,
2698
+ projectName: projectName2,
2699
+ updatedAt
2700
+ }) => {
2701
+ const messages = state.messages.filter((message) => message.content.trim());
2702
+ if (!messages.length && !state.threadId) {
2703
+ return null;
2704
+ }
2705
+ const firstUserMessage = messages.find((message) => message.role === "user");
2706
+ const fallbackTitle = state.threadId ?? "New local session";
2707
+ const title = truncateForTerminal(
2708
+ firstUserMessage?.content.trim() || messages[0]?.content.trim() || fallbackTitle,
2709
+ 72
2710
+ );
2711
+ const messageTimestamps = messages.map((message) => message.updatedAt ?? message.createdAt).filter((value) => Number.isFinite(value));
2712
+ const lastUpdated = updatedAt ?? (messageTimestamps.length ? Math.max(...messageTimestamps) : Date.now());
2713
+ return {
2714
+ key,
2715
+ title,
2716
+ threadId: state.threadId,
2717
+ updatedAt: lastUpdated,
2718
+ projectName: projectName2,
2719
+ messageCount: messages.length,
2720
+ status: state.status
2721
+ };
2722
+ };
2136
2723
  var buildThreadSelectItems = (sessions, activeThreadId, remoteThreads = [], options = {}) => [
2137
2724
  {
2138
2725
  label: "New thread",
2139
2726
  value: { kind: "new" },
2140
2727
  description: activeThreadId ? "Start a fresh CloudEval chat thread." : "Current selection."
2141
2728
  },
2729
+ ...(options.drafts ?? []).map((draft) => {
2730
+ const age = draft.updatedAt ? relativeThreadAge(new Date(draft.updatedAt).toISOString(), options.now) : void 0;
2731
+ return {
2732
+ label: titleWithAge(draft.title, age),
2733
+ value: { kind: "draft", draft },
2734
+ description: draftDescription(draft, age)
2735
+ };
2736
+ }),
2142
2737
  ...remoteThreads.map((thread) => {
2143
2738
  const age = relativeThreadAge(thread.created_at ?? thread.updated_at, options.now);
2144
2739
  return {
@@ -2440,7 +3035,7 @@ var firstNumber2 = (value, keys) => {
2440
3035
  }
2441
3036
  return void 0;
2442
3037
  };
2443
- var firstString = (value, keys) => {
3038
+ var firstString2 = (value, keys) => {
2444
3039
  const record = toRecord2(value);
2445
3040
  if (!record) {
2446
3041
  return void 0;
@@ -2519,7 +3114,7 @@ var extractPillarScores = (report) => {
2519
3114
  const rows = pillarArray.map((item) => {
2520
3115
  const record = toRecord2(item);
2521
3116
  if (!record) return void 0;
2522
- const label = firstString(record, ["label", "id", "pillar"]);
3117
+ const label = firstString2(record, ["label", "id", "pillar"]);
2523
3118
  const value = firstNumber2(record, ["score", "value"]);
2524
3119
  return label && value !== void 0 ? { [label]: value } : void 0;
2525
3120
  }).filter(Boolean).reduce((acc, row) => ({ ...acc, ...row }), {});
@@ -2558,7 +3153,7 @@ var extractMonthlySavings = (report) => firstNumber2(firstRecord2(report, ["pars
2558
3153
  ]) ?? firstNumber2(report, ["total_monthly_savings", "monthly_savings"]);
2559
3154
  var extractCurrency = (...values) => {
2560
3155
  for (const value of values) {
2561
- const currency = firstString(value, ["currency"]) ?? firstString(firstRecord2(value, ["parsed", "totalSpend"]), ["currency"]) ?? firstString(firstRecord2(value, ["dashboard"]), ["currency"]) ?? firstString(firstRecord2(value, ["processed"]), ["currency"]);
3156
+ const currency = firstString2(value, ["currency"]) ?? firstString2(firstRecord2(value, ["parsed", "totalSpend"]), ["currency"]) ?? firstString2(firstRecord2(value, ["dashboard"]), ["currency"]) ?? firstString2(firstRecord2(value, ["processed"]), ["currency"]);
2562
3157
  if (currency) {
2563
3158
  return currency;
2564
3159
  }
@@ -2617,7 +3212,7 @@ var projectHealthRows = (reportsSummary, selectedProjectId) => firstArray(report
2617
3212
  architectureStatus: String(record.architecture_status ?? "not_started"),
2618
3213
  unitTestsStatus: String(record.unit_tests_status ?? "not_started"),
2619
3214
  freshness: String(record.freshness ?? "missing"),
2620
- lastReportAt: firstString(record, ["last_report_at"]),
3215
+ lastReportAt: firstString2(record, ["last_report_at"]),
2621
3216
  criticalIssues: firstNumber2(record, ["critical_issues"]) ?? 0,
2622
3217
  coveragePercent: firstNumber2(record, ["coverage_percent"]) ?? 0,
2623
3218
  isSelected: Boolean(selectedProjectId && projectId === selectedProjectId)
@@ -2797,8 +3392,8 @@ var buildReportsDashboardModel = ({
2797
3392
  return {
2798
3393
  label: String(record.label ?? "Action"),
2799
3394
  issueCount: firstNumber2(record, ["issue_count"]),
2800
- priority: firstString(record, ["priority"]),
2801
- pillar: firstString(record, ["pillar"])
3395
+ priority: firstString2(record, ["priority"]),
3396
+ pillar: firstString2(record, ["pillar"])
2802
3397
  };
2803
3398
  }).filter((action) => action.label.trim()).slice(0, 20),
2804
3399
  topInsights: firstArray(reportsSummary, ["top_insights"]).map((insight) => String(insight)).filter(Boolean).slice(0, 20)
@@ -2906,7 +3501,7 @@ var directArray = (value) => {
2906
3501
  }
2907
3502
  return [];
2908
3503
  };
2909
- var firstString2 = (value, keys, fallback = "") => {
3504
+ var firstString3 = (value, keys, fallback = "") => {
2910
3505
  const record = toRecord3(value);
2911
3506
  if (!record) {
2912
3507
  return fallback;
@@ -2972,11 +3567,11 @@ var buildReportsSummaryFromHistory = ({
2972
3567
  const statusBreakdown = {};
2973
3568
  const rowsByProject = /* @__PURE__ */ new Map();
2974
3569
  for (const item of rows) {
2975
- const reportType = normalizeReportType(firstString2(item, ["report_type", "kind"], "cost"));
2976
- const status = firstString2(item, ["status"], "completed").toLowerCase();
2977
- const projectId = firstString2(item, ["project_id", "projectId"], "unknown-project");
2978
- const projectName2 = firstString2(item, ["project_name", "projectName"]) || projects.find((project) => project.id === projectId)?.name || projectId;
2979
- const generatedAt = firstString2(item, ["generated_at", "generatedAt"]);
3570
+ const reportType = normalizeReportType(firstString3(item, ["report_type", "kind"], "cost"));
3571
+ const status = firstString3(item, ["status"], "completed").toLowerCase();
3572
+ const projectId = firstString3(item, ["project_id", "projectId"], "unknown-project");
3573
+ const projectName2 = firstString3(item, ["project_name", "projectName"]) || projects.find((project) => project.id === projectId)?.name || projectId;
3574
+ const generatedAt = firstString3(item, ["generated_at", "generatedAt"]);
2980
3575
  const metrics = toRecord3((toRecord3(item) ?? {}).metrics) ?? {};
2981
3576
  const existing = rowsByProject.get(projectId) ?? {
2982
3577
  project_id: projectId,
@@ -3077,7 +3672,7 @@ var loadWorkspacePanelData = async ({
3077
3672
  return void 0;
3078
3673
  }
3079
3674
  };
3080
- if (tab === "overview" || tab === "connections") {
3675
+ if (tab === "overview" || tab === "connections" || tab === "projects") {
3081
3676
  if (currentUserId) {
3082
3677
  await capture(
3083
3678
  "connections",
@@ -3211,7 +3806,8 @@ var loadWorkspacePanelData = async ({
3211
3806
  import React7 from "react";
3212
3807
  import { Box as Box8, Text as Text9 } from "ink";
3213
3808
  import { plot as plotAsciiChart } from "asciichart";
3214
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
3809
+
3810
+ // src/ui/workspaceEntityDetails.ts
3215
3811
  var toRecord4 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3216
3812
  var directArray2 = (value) => {
3217
3813
  if (Array.isArray(value)) {
@@ -3221,6 +3817,199 @@ var directArray2 = (value) => {
3221
3817
  if (!record) {
3222
3818
  return [];
3223
3819
  }
3820
+ for (const key of ["items", "data", "rows", "results", "connections", "projects"]) {
3821
+ const candidate = record[key];
3822
+ if (Array.isArray(candidate)) {
3823
+ return candidate;
3824
+ }
3825
+ }
3826
+ return [];
3827
+ };
3828
+ var firstString4 = (value, keys, fallback = "") => {
3829
+ const record = toRecord4(value);
3830
+ if (!record) {
3831
+ return fallback;
3832
+ }
3833
+ for (const key of keys) {
3834
+ const current = record[key];
3835
+ if (typeof current === "string" && current.trim()) {
3836
+ return current.trim();
3837
+ }
3838
+ if (typeof current === "number" && Number.isFinite(current)) {
3839
+ return String(current);
3840
+ }
3841
+ if (typeof current === "boolean") {
3842
+ return current ? "on" : "off";
3843
+ }
3844
+ }
3845
+ return fallback;
3846
+ };
3847
+ var firstNumber4 = (value, keys) => {
3848
+ const record = toRecord4(value);
3849
+ if (!record) {
3850
+ return void 0;
3851
+ }
3852
+ for (const key of keys) {
3853
+ const current = record[key];
3854
+ const numberValue = typeof current === "number" ? current : Number(current);
3855
+ if (Number.isFinite(numberValue)) {
3856
+ return numberValue;
3857
+ }
3858
+ }
3859
+ return void 0;
3860
+ };
3861
+ var statusTone = (value) => {
3862
+ const normalized = value.toLowerCase();
3863
+ if (["completed", "ready", "fresh", "synced", "success", "active"].includes(normalized)) {
3864
+ return "success";
3865
+ }
3866
+ if (["running", "partial", "pending", "stale"].includes(normalized)) {
3867
+ return "warning";
3868
+ }
3869
+ if (["failed", "error", "missing", "outdated", "disabled"].includes(normalized)) {
3870
+ return "danger";
3871
+ }
3872
+ return "muted";
3873
+ };
3874
+ var truncateDetail = (value, limit = 96) => value.length <= limit ? value : `${value.slice(0, limit - 3)}...`;
3875
+ var projectIdsForConnection = (connection) => {
3876
+ const record = toRecord4(connection);
3877
+ if (!record) {
3878
+ return [];
3879
+ }
3880
+ const values = [
3881
+ record.project_id,
3882
+ record.projectId,
3883
+ record.project,
3884
+ record.project_name
3885
+ ];
3886
+ const arrayValues = [
3887
+ record.project_ids,
3888
+ record.projectIds,
3889
+ record.projects,
3890
+ record.linked_project_ids
3891
+ ].flatMap((value) => directArray2(value));
3892
+ return [...values, ...arrayValues].map((value) => firstString4({ value }, ["value"])).filter(Boolean);
3893
+ };
3894
+ var connectionMatchesProject = (connection, project) => {
3895
+ const haystack = projectIdsForConnection(connection).map((value) => value.toLowerCase());
3896
+ const id = String(project.id ?? "").toLowerCase();
3897
+ const name = String(project.name ?? "").toLowerCase();
3898
+ return Boolean(id && haystack.includes(id) || name && haystack.includes(name));
3899
+ };
3900
+ var projectHealthFor = (reportsSummary, project) => {
3901
+ const rows = directArray2(toRecord4(reportsSummary)?.project_health);
3902
+ return rows.map(toRecord4).find((row) => {
3903
+ if (!row) {
3904
+ return false;
3905
+ }
3906
+ return String(row.project_id ?? row.projectId ?? "").toLowerCase() === String(project.id ?? "").toLowerCase() || String(row.project_name ?? row.projectName ?? "").toLowerCase() === String(project.name ?? "").toLowerCase();
3907
+ });
3908
+ };
3909
+ var buildProjectDetailModel = ({
3910
+ project,
3911
+ connections,
3912
+ reportsSummary
3913
+ }) => {
3914
+ if (!project) {
3915
+ return null;
3916
+ }
3917
+ const linkedConnections = connections.filter(
3918
+ (connection) => connectionMatchesProject(connection, project)
3919
+ );
3920
+ const status = firstString4(project, ["status", "sync_status", "last_sync_status"], "unknown");
3921
+ const health = projectHealthFor(reportsSummary, project);
3922
+ const coverage = firstNumber4(health, ["coverage_percent", "coveragePercent"]);
3923
+ const critical = firstNumber4(health, ["critical_issues", "criticalIssues", "critical_count"]);
3924
+ const lastReport = firstString4(health, ["last_report_at", "lastReportAt", "generated_at"]);
3925
+ return {
3926
+ title: firstString4(project, ["name"], "Project"),
3927
+ subtitle: firstString4(project, ["id"]),
3928
+ metrics: [
3929
+ {
3930
+ label: "Provider",
3931
+ value: firstString4(project, ["cloud_provider", "provider"], "cloud"),
3932
+ tone: "brand"
3933
+ },
3934
+ { label: "Type", value: firstString4(project, ["type", "source_type"], "project") },
3935
+ { label: "Status", value: status, tone: statusTone(status) },
3936
+ { label: "Connections", value: String(linkedConnections.length), tone: "brand" }
3937
+ ],
3938
+ detailRows: [
3939
+ { label: "Created", value: firstString4(project, ["created_at", "createdAt"], "-") },
3940
+ { label: "Updated", value: firstString4(project, ["updated_at", "updatedAt"], "-") },
3941
+ ...coverage !== void 0 ? [{ label: "Report coverage", value: `${Math.round(coverage)}%`, tone: "brand" }] : [],
3942
+ ...critical !== void 0 ? [{ label: "Critical issues", value: String(critical), tone: critical > 0 ? "danger" : "success" }] : [],
3943
+ ...lastReport ? [{ label: "Last report", value: lastReport }] : []
3944
+ ],
3945
+ relatedItems: linkedConnections.map((connection) => ({
3946
+ label: firstString4(connection, ["name"], "Connection"),
3947
+ detail: firstString4(connection, ["last_sync_status", "status", "type"], ""),
3948
+ tone: statusTone(firstString4(connection, ["last_sync_status", "status"], ""))
3949
+ }))
3950
+ };
3951
+ };
3952
+ var linkedProjectForConnection = (connection, projects) => {
3953
+ const ids = projectIdsForConnection(connection).map((value) => value.toLowerCase());
3954
+ return projects.find((project) => {
3955
+ const id = String(project.id ?? "").toLowerCase();
3956
+ const name = String(project.name ?? "").toLowerCase();
3957
+ return id && ids.includes(id) || name && ids.includes(name);
3958
+ });
3959
+ };
3960
+ var buildConnectionDetailModel = ({
3961
+ connection,
3962
+ projects
3963
+ }) => {
3964
+ const record = toRecord4(connection);
3965
+ if (!record) {
3966
+ return null;
3967
+ }
3968
+ const linkedProject = linkedProjectForConnection(connection, projects);
3969
+ const sync = firstString4(connection, ["last_sync_status", "sync_status", "status"], "unknown");
3970
+ const autoSync = firstString4(connection, ["auto_sync", "autoSync"], "off");
3971
+ const source = firstString4(connection, [
3972
+ "template_url",
3973
+ "source_url",
3974
+ "repo_url",
3975
+ "repository_url",
3976
+ "visualization_source_path",
3977
+ "workspace_file_paths"
3978
+ ]);
3979
+ return {
3980
+ title: firstString4(connection, ["name"], "Connection"),
3981
+ subtitle: firstString4(connection, ["id"]),
3982
+ metrics: [
3983
+ {
3984
+ label: "Provider",
3985
+ value: firstString4(connection, ["cloud_provider", "provider"], "cloud"),
3986
+ tone: "brand"
3987
+ },
3988
+ { label: "Type", value: firstString4(connection, ["type", "connection_type"], "connection") },
3989
+ { label: "Sync", value: sync, tone: statusTone(sync) },
3990
+ { label: "Auto", value: autoSync, tone: autoSync === "on" ? "success" : "muted" }
3991
+ ],
3992
+ detailRows: [
3993
+ { label: "Project", value: linkedProject?.name ?? firstString4(connection, ["project_name", "project_id"], "-") },
3994
+ { label: "Last synced", value: firstString4(connection, ["last_synced", "lastSynced", "updated_at"], "-") },
3995
+ ...source ? [{ label: "Source", value: truncateDetail(source), tone: "brand" }] : [],
3996
+ { label: "Created", value: firstString4(connection, ["created_at", "createdAt"], "-") }
3997
+ ],
3998
+ relatedItems: linkedProject ? [{ label: linkedProject.name, detail: linkedProject.cloud_provider ?? "project", tone: "brand" }] : []
3999
+ };
4000
+ };
4001
+
4002
+ // src/ui/workspacePanel.tsx
4003
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
4004
+ var toRecord5 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4005
+ var directArray3 = (value) => {
4006
+ if (Array.isArray(value)) {
4007
+ return value;
4008
+ }
4009
+ const record = toRecord5(value);
4010
+ if (!record) {
4011
+ return [];
4012
+ }
3224
4013
  for (const key of [
3225
4014
  "data",
3226
4015
  "items",
@@ -3240,9 +4029,9 @@ var directArray2 = (value) => {
3240
4029
  if (Array.isArray(candidate)) {
3241
4030
  return candidate;
3242
4031
  }
3243
- const nested = toRecord4(candidate);
4032
+ const nested = toRecord5(candidate);
3244
4033
  if (nested) {
3245
- const nestedArray = directArray2(nested);
4034
+ const nestedArray = directArray3(nested);
3246
4035
  if (nestedArray.length) {
3247
4036
  return nestedArray;
3248
4037
  }
@@ -3266,7 +4055,7 @@ var allNumbers = (value, limit = 36) => {
3266
4055
  }
3267
4056
  return;
3268
4057
  }
3269
- const record = toRecord4(current);
4058
+ const record = toRecord5(current);
3270
4059
  if (!record) {
3271
4060
  return;
3272
4061
  }
@@ -3281,8 +4070,8 @@ var allNumbers = (value, limit = 36) => {
3281
4070
  visit(value);
3282
4071
  return output;
3283
4072
  };
3284
- var firstString3 = (value, keys, fallback = "unknown") => {
3285
- const record = toRecord4(value);
4073
+ var firstString5 = (value, keys, fallback = "unknown") => {
4074
+ const record = toRecord5(value);
3286
4075
  if (!record) {
3287
4076
  return fallback;
3288
4077
  }
@@ -3297,8 +4086,8 @@ var firstString3 = (value, keys, fallback = "unknown") => {
3297
4086
  }
3298
4087
  return fallback;
3299
4088
  };
3300
- var firstNumber4 = (value, keys) => {
3301
- const record = toRecord4(value);
4089
+ var firstNumber5 = (value, keys) => {
4090
+ const record = toRecord5(value);
3302
4091
  if (!record) {
3303
4092
  return void 0;
3304
4093
  }
@@ -3312,14 +4101,14 @@ var firstNumber4 = (value, keys) => {
3312
4101
  return void 0;
3313
4102
  };
3314
4103
  var billingUsageTrendValues = (usageSummary) => {
3315
- const record = toRecord4(usageSummary);
3316
- const buckets = directArray2(record?.buckets);
3317
- const bucketValues = buckets.map((bucket) => firstNumber4(bucket, ["credits_used", "total_credits", "credits"])).filter((value) => value !== void 0);
4104
+ const record = toRecord5(usageSummary);
4105
+ const buckets = directArray3(record?.buckets);
4106
+ const bucketValues = buckets.map((bucket) => firstNumber5(bucket, ["credits_used", "total_credits", "credits"])).filter((value) => value !== void 0);
3318
4107
  if (bucketValues.length) {
3319
4108
  return bucketValues;
3320
4109
  }
3321
- const totals = toRecord4(record?.totals);
3322
- const total = firstNumber4(totals ?? usageSummary, ["credits_used", "total_credits", "credits"]);
4110
+ const totals = toRecord5(record?.totals);
4111
+ const total = firstNumber5(totals ?? usageSummary, ["credits_used", "total_credits", "credits"]);
3323
4112
  return total !== void 0 ? [total] : allNumbers(usageSummary);
3324
4113
  };
3325
4114
  var formatNumber3 = (value) => value === void 0 ? "-" : new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format(
@@ -3327,6 +4116,8 @@ var formatNumber3 = (value) => value === void 0 ? "-" : new Intl.NumberFormat("e
3327
4116
  );
3328
4117
  var formatPercent = (value) => value === void 0 ? "-" : `${Math.round(value * 100)}%`;
3329
4118
  var metricColor = (tone) => {
4119
+ if (tone === "brand") return terminalTheme.brand;
4120
+ if (tone === "muted") return terminalTheme.muted;
3330
4121
  if (tone === "success") return terminalTheme.success;
3331
4122
  if (tone === "warning") return terminalTheme.warning;
3332
4123
  if (tone === "danger") return terminalTheme.danger;
@@ -3407,7 +4198,7 @@ var AsciiLineChart = ({ values, width, height = 4, tone }) => {
3407
4198
  )) });
3408
4199
  };
3409
4200
  var keySummary = (value) => {
3410
- const record = toRecord4(value);
4201
+ const record = toRecord5(value);
3411
4202
  if (!record) {
3412
4203
  return "no object payload";
3413
4204
  }
@@ -3415,11 +4206,11 @@ var keySummary = (value) => {
3415
4206
  return keys.length ? keys.join(", ") : "empty object";
3416
4207
  };
3417
4208
  var rowLabel = (value, fallback) => {
3418
- const record = toRecord4(value);
4209
+ const record = toRecord5(value);
3419
4210
  if (!record) {
3420
4211
  return typeof value === "string" ? value : fallback;
3421
4212
  }
3422
- return firstString3(
4213
+ return firstString5(
3423
4214
  record,
3424
4215
  [
3425
4216
  "name",
@@ -3435,21 +4226,21 @@ var rowLabel = (value, fallback) => {
3435
4226
  );
3436
4227
  };
3437
4228
  var rowDetail = (value) => {
3438
- const record = toRecord4(value);
4229
+ const record = toRecord5(value);
3439
4230
  if (!record) {
3440
4231
  return "";
3441
4232
  }
3442
4233
  const parts = [
3443
- firstString3(record, ["cloud_provider", "provider"], ""),
4234
+ firstString5(record, ["cloud_provider", "provider"], ""),
3444
4235
  (() => {
3445
- const status = firstString3(
4236
+ const status = firstString5(
3446
4237
  record,
3447
4238
  ["effective_status", "status", "outcome"],
3448
4239
  ""
3449
4240
  );
3450
4241
  return status.toLowerCase().replace(/[\s-]+/g, "_") === "trial_active" ? "" : status;
3451
4242
  })(),
3452
- firstString3(
4243
+ firstString5(
3453
4244
  record,
3454
4245
  ["created_at", "updated_at", "timestamp", "period"],
3455
4246
  ""
@@ -3543,11 +4334,9 @@ var BillingSummaryLine = ({
3543
4334
  /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
3544
4335
  "Credits:",
3545
4336
  " ",
3546
- /* @__PURE__ */ jsxs7(Text9, { color: billingToneColor(billing.tone), children: [
3547
- formatCredits(billing.remaining),
3548
- "/",
3549
- formatCredits(billing.total)
3550
- ] })
4337
+ /* @__PURE__ */ jsx9(Text9, { color: billingToneColor(billing.tone), children: Math.max(Number(billing.reportedUsed ?? billing.used ?? 0), 0) > 0 ? `${formatCredits(billing.remaining)} left | Used ${formatCredits(
4338
+ Math.max(Number(billing.reportedUsed ?? billing.used ?? 0), 0)
4339
+ )}` : `${formatCredits(billing.remaining)}/${formatCredits(billing.total)}` })
3551
4340
  ] }),
3552
4341
  /* @__PURE__ */ jsx9(
3553
4342
  CreditProgress,
@@ -3712,7 +4501,7 @@ var HelpLegend = ({
3712
4501
  ] }, segment.key)) });
3713
4502
  };
3714
4503
  var ResourceSummary = ({ label, value, terminalColumns }) => {
3715
- const rows = directArray2(value);
4504
+ const rows = directArray3(value);
3716
4505
  return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
3717
4506
  /* @__PURE__ */ jsx9(Text9, { bold: true, children: label }),
3718
4507
  /* @__PURE__ */ jsx9(Text9, { dimColor: true, wrap: "wrap", children: rows.length ? `${rows.length} row(s) returned` : `keys: ${truncateForTerminal(keySummary(value), Math.max(32, terminalColumns - 14))}` }),
@@ -3723,86 +4512,185 @@ var ResourceSummary = ({ label, value, terminalColumns }) => {
3723
4512
  truncateForTerminal(rowLabel(row, `row ${index + 1}`), 36),
3724
4513
  rowDetail(row) ? ` - ${truncateForTerminal(rowDetail(row), 64)}` : ""
3725
4514
  ] }, `${label}-${index}`)),
3726
- !rows.length && !toRecord4(value) ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "No data returned." }) : null
4515
+ !rows.length && !toRecord5(value) ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "No data returned." }) : null
4516
+ ] });
4517
+ };
4518
+ var cleanBackendWarning = (warning, terminalColumns) => {
4519
+ const compact = warning.replace(
4520
+ /:\s*\{"detail":"Device token not authorized for this endpoint"\}/g,
4521
+ " (backend denied CLI device-token access)"
4522
+ ).replace(/\s+/g, " ").trim();
4523
+ return truncateForTerminal(
4524
+ compact,
4525
+ Math.max(72, Math.min(180, terminalColumns - 8))
4526
+ );
4527
+ };
4528
+ var entityToneColor = (tone) => {
4529
+ if (tone === "brand") return terminalTheme.brand;
4530
+ if (tone === "success") return terminalTheme.success;
4531
+ if (tone === "warning") return terminalTheme.warning;
4532
+ if (tone === "danger") return terminalTheme.danger;
4533
+ return terminalTheme.muted;
4534
+ };
4535
+ var EntityDetailPanel = ({ model, emptyLabel, compact, terminalColumns }) => {
4536
+ if (!model) {
4537
+ return /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: emptyLabel });
4538
+ }
4539
+ return /* @__PURE__ */ jsx9(SectionCard, { title: "Selected detail", borderColor: terminalTheme.focus, children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
4540
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: compact ? "column" : "row", justifyContent: "space-between", gap: 1, children: [
4541
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", flexShrink: 1, children: [
4542
+ /* @__PURE__ */ jsx9(Text9, { color: terminalTheme.focus, bold: true, wrap: "truncate", children: truncateForTerminal(model.title, Math.max(24, terminalColumns - 32)) }),
4543
+ model.subtitle ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, wrap: "truncate", children: model.subtitle }) : null
4544
+ ] }),
4545
+ /* @__PURE__ */ jsx9(MetricStrip, { metrics: model.metrics, compact })
4546
+ ] }),
4547
+ /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", children: model.detailRows.map((row) => /* @__PURE__ */ jsxs7(Text9, { wrap: "truncate", children: [
4548
+ /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
4549
+ row.label.padEnd(16),
4550
+ " "
4551
+ ] }),
4552
+ /* @__PURE__ */ jsx9(Text9, { color: entityToneColor(row.tone), children: truncateForTerminal(row.value || "-", Math.max(20, terminalColumns - 24)) })
4553
+ ] }, `${row.label}-${row.value}`)) }),
4554
+ model.relatedItems.length ? /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
4555
+ /* @__PURE__ */ jsx9(Text9, { color: terminalTheme.brand, children: "Linked" }),
4556
+ model.relatedItems.slice(0, 5).map((item, index) => /* @__PURE__ */ jsxs7(Text9, { wrap: "truncate", children: [
4557
+ /* @__PURE__ */ jsx9(Text9, { color: entityToneColor(item.tone), children: "\u25CF " }),
4558
+ truncateForTerminal(item.label, 36),
4559
+ item.detail ? /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
4560
+ " - ",
4561
+ truncateForTerminal(item.detail, 36)
4562
+ ] }) : null
4563
+ ] }, `${item.label}-${index}`))
4564
+ ] }) : null
4565
+ ] }) });
4566
+ };
4567
+ var ProjectsView = ({ projects, selectedProject, connections, reportsSummary, compact, terminalColumns }) => {
4568
+ const selectedModel = buildProjectDetailModel({
4569
+ project: selectedProject ?? projects[0],
4570
+ connections,
4571
+ reportsSummary
4572
+ });
4573
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
4574
+ /* @__PURE__ */ jsx9(
4575
+ MetricStrip,
4576
+ {
4577
+ compact,
4578
+ metrics: [
4579
+ { label: "Projects", value: String(projects.length) },
4580
+ { label: "Selected", value: selectedProject?.name ?? "none" }
4581
+ ]
4582
+ }
4583
+ ),
4584
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "J/K or Up/Down selects project | Enter confirms | O opens frontend" }),
4585
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: compact ? "column" : "row", gap: 2, children: [
4586
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", flexBasis: compact ? void 0 : 52, flexShrink: 0, children: [
4587
+ /* @__PURE__ */ jsx9(Text9, { color: terminalTheme.brand, children: "Project list" }),
4588
+ projects.length ? projects.slice(0, 12).map((project, index) => {
4589
+ const active = project.id === (selectedProject ?? projects[0])?.id;
4590
+ return /* @__PURE__ */ jsxs7(
4591
+ Text9,
4592
+ {
4593
+ color: active ? terminalTheme.focus : void 0,
4594
+ backgroundColor: active ? terminalTheme.selectedBackground : void 0,
4595
+ bold: active,
4596
+ wrap: "truncate",
4597
+ children: [
4598
+ active ? ">" : " ",
4599
+ " ",
4600
+ truncateForTerminal(project.name, 30),
4601
+ " ",
4602
+ /* @__PURE__ */ jsx9(Text9, { color: terminalTheme.brand, children: project.cloud_provider ?? "cloud" }),
4603
+ " ",
4604
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: project.id })
4605
+ ]
4606
+ },
4607
+ project.id ?? index
4608
+ );
4609
+ }) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "No projects returned by the backend." })
4610
+ ] }),
4611
+ /* @__PURE__ */ jsx9(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx9(
4612
+ EntityDetailPanel,
4613
+ {
4614
+ model: selectedModel,
4615
+ emptyLabel: "Select a project to inspect backend details.",
4616
+ compact,
4617
+ terminalColumns: Math.max(32, terminalColumns - 56)
4618
+ }
4619
+ ) })
4620
+ ] })
4621
+ ] });
4622
+ };
4623
+ var ConnectionsView = ({ connections, projects, selectedIndex, compact, terminalColumns }) => {
4624
+ const safeIndex = connections.length ? Math.min(Math.max(0, selectedIndex), connections.length - 1) : -1;
4625
+ const selectedConnection = safeIndex >= 0 ? connections[safeIndex] : void 0;
4626
+ const selectedModel = buildConnectionDetailModel({
4627
+ connection: selectedConnection,
4628
+ projects
4629
+ });
4630
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
4631
+ /* @__PURE__ */ jsx9(
4632
+ MetricStrip,
4633
+ {
4634
+ compact,
4635
+ metrics: [{ label: "Connections", value: String(connections.length) }]
4636
+ }
4637
+ ),
4638
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "J/K or Up/Down selects connection | Enter confirms | O opens frontend | D downloads table data" }),
4639
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: compact ? "column" : "row", gap: 2, children: [
4640
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", flexBasis: compact ? void 0 : 58, flexShrink: 0, children: [
4641
+ /* @__PURE__ */ jsx9(Text9, { color: terminalTheme.brand, children: "Connection list" }),
4642
+ connections.length ? connections.slice(0, 12).map((connection, index) => {
4643
+ const active = index === safeIndex;
4644
+ return /* @__PURE__ */ jsxs7(
4645
+ Text9,
4646
+ {
4647
+ color: active ? terminalTheme.focus : void 0,
4648
+ backgroundColor: active ? terminalTheme.selectedBackground : void 0,
4649
+ bold: active,
4650
+ wrap: "truncate",
4651
+ children: [
4652
+ active ? ">" : " ",
4653
+ " ",
4654
+ String(index + 1).padStart(2),
4655
+ " ",
4656
+ truncateForTerminal(rowLabel(connection, "connection"), 34),
4657
+ rowDetail(connection) ? /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
4658
+ " - ",
4659
+ truncateForTerminal(rowDetail(connection), 28)
4660
+ ] }) : ""
4661
+ ]
4662
+ },
4663
+ String(firstString5(connection, ["id"], String(index)))
4664
+ );
4665
+ }) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "No connections returned by the backend." })
4666
+ ] }),
4667
+ /* @__PURE__ */ jsx9(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx9(
4668
+ EntityDetailPanel,
4669
+ {
4670
+ model: selectedModel,
4671
+ emptyLabel: "Select a connection to inspect backend details.",
4672
+ compact,
4673
+ terminalColumns: Math.max(32, terminalColumns - 62)
4674
+ }
4675
+ ) })
4676
+ ] })
3727
4677
  ] });
3728
4678
  };
3729
- var cleanBackendWarning = (warning, terminalColumns) => {
3730
- const compact = warning.replace(
3731
- /:\s*\{"detail":"Device token not authorized for this endpoint"\}/g,
3732
- " (backend denied CLI device-token access)"
3733
- ).replace(/\s+/g, " ").trim();
3734
- return truncateForTerminal(
3735
- compact,
3736
- Math.max(72, Math.min(180, terminalColumns - 8))
3737
- );
3738
- };
3739
- var ProjectsView = ({ projects, selectedProject }) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
3740
- /* @__PURE__ */ jsx9(
3741
- MetricStrip,
3742
- {
3743
- compact: false,
3744
- metrics: [
3745
- { label: "Projects", value: String(projects.length) },
3746
- { label: "Selected", value: selectedProject?.name ?? "none" }
3747
- ]
3748
- }
3749
- ),
3750
- projects.length ? projects.slice(0, 12).map((project, index) => /* @__PURE__ */ jsxs7(
3751
- Text9,
3752
- {
3753
- color: project.id === selectedProject?.id ? terminalTheme.brand : void 0,
3754
- wrap: "truncate",
3755
- children: [
3756
- project.id === selectedProject?.id ? ">" : " ",
3757
- " ",
3758
- project.name,
3759
- " |",
3760
- " ",
3761
- project.cloud_provider ?? "cloud",
3762
- " | ",
3763
- project.id
3764
- ]
3765
- },
3766
- project.id ?? index
3767
- )) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "No projects returned by the backend." })
3768
- ] });
3769
- var ConnectionsView = ({ connections }) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
3770
- /* @__PURE__ */ jsx9(
3771
- MetricStrip,
3772
- {
3773
- compact: false,
3774
- metrics: [{ label: "Connections", value: String(connections.length) }]
3775
- }
3776
- ),
3777
- connections.length ? connections.slice(0, 12).map((connection, index) => /* @__PURE__ */ jsxs7(
3778
- Text9,
3779
- {
3780
- wrap: "truncate",
3781
- children: [
3782
- index + 1,
3783
- ".",
3784
- " ",
3785
- truncateForTerminal(rowLabel(connection, "connection"), 36),
3786
- rowDetail(connection) ? ` - ${truncateForTerminal(rowDetail(connection), 72)}` : ""
3787
- ]
3788
- },
3789
- String(firstString3(connection, ["id"], String(index)))
3790
- )) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "No connections returned by the backend." })
3791
- ] });
3792
4679
  var BillingView = ({ state, compact, terminalColumns, frontendUrl }) => {
3793
- const creditStatus = toRecord4(state.data.creditStatus);
3794
- const entitlement = toRecord4(state.data.entitlement);
3795
- const plan = toRecord4(entitlement?.plan);
4680
+ const creditStatus = toRecord5(state.data.creditStatus);
4681
+ const entitlement = toRecord5(state.data.entitlement);
4682
+ const plan = toRecord5(entitlement?.plan);
3796
4683
  const usageValues = billingUsageTrendValues(state.data.usageSummary);
3797
- const ledger = directArray2(state.data.ledger);
3798
- const invoices = directArray2(state.data.billingInfo);
3799
- const topups = directArray2(state.data.topups);
3800
- const notifications = directArray2(state.data.notifications);
3801
- const remainingRatio = firstNumber4(creditStatus, ["remainingRatio"]);
3802
- const creditTone = firstString3(creditStatus, ["tone"], "normal");
4684
+ const ledger = directArray3(state.data.ledger);
4685
+ const invoices = directArray3(state.data.billingInfo);
4686
+ const topups = directArray3(state.data.topups);
4687
+ const notifications = directArray3(state.data.notifications);
4688
+ const remainingRatio = firstNumber5(creditStatus, ["remainingRatio"]);
4689
+ const creditTone = firstString5(creditStatus, ["tone"], "normal");
3803
4690
  const tone = metricToneFromBillingTone(creditTone);
3804
- const remaining = firstNumber4(creditStatus, ["remaining"]);
3805
- const total = firstNumber4(creditStatus, ["total"]);
4691
+ const remaining = firstNumber5(creditStatus, ["remaining"]);
4692
+ const total = firstNumber5(creditStatus, ["total"]);
4693
+ const reportedUsed = firstNumber5(creditStatus, ["reportedUsed"]) ?? firstNumber5(creditStatus, ["used"]);
3806
4694
  const plansUrl = buildFrontendUrl({
3807
4695
  baseUrl: frontendUrl,
3808
4696
  target: "billing",
@@ -3816,17 +4704,18 @@ var BillingView = ({ state, compact, terminalColumns, frontendUrl }) => {
3816
4704
  const metrics = [
3817
4705
  {
3818
4706
  label: "Plan",
3819
- value: firstString3(
4707
+ value: firstString5(
3820
4708
  creditStatus,
3821
4709
  ["planName"],
3822
- firstString3(plan, ["name"], "-")
4710
+ firstString5(plan, ["name"], "-")
3823
4711
  )
3824
4712
  },
3825
4713
  { label: "Remaining", value: formatNumber3(remaining) },
3826
- { label: "Used", value: formatNumber3(firstNumber4(creditStatus, ["used"])) },
4714
+ { label: "Included", value: formatNumber3(total) },
4715
+ { label: "Used", value: formatNumber3(reportedUsed) },
3827
4716
  {
3828
4717
  label: "Top-up",
3829
- value: formatNumber3(firstNumber4(creditStatus, ["topUpBalance"]))
4718
+ value: formatNumber3(firstNumber5(creditStatus, ["topUpBalance"]))
3830
4719
  }
3831
4720
  ];
3832
4721
  return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
@@ -4058,7 +4947,7 @@ var ReportsView = ({
4058
4947
  const chartWidth = compact ? Math.max(24, terminalColumns - 28) : 42;
4059
4948
  const selectedSummary = model.selectedProjectSummary;
4060
4949
  const selectedStatuses = selectedSummary.reportStatuses;
4061
- const hasReportData = Boolean(toRecord4(state.data.reportsSummary));
4950
+ const hasReportData = Boolean(toRecord5(state.data.reportsSummary));
4062
4951
  return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
4063
4952
  /* @__PURE__ */ jsxs7(
4064
4953
  Box8,
@@ -4279,7 +5168,7 @@ var OverviewView = ({
4279
5168
  terminalColumns,
4280
5169
  tablePage
4281
5170
  }) => {
4282
- const connections = directArray2(state.data.connections);
5171
+ const connections = directArray3(state.data.connections);
4283
5172
  const model = buildOverviewDashboardModel({
4284
5173
  dashboard: state.data.dashboard,
4285
5174
  reportsSummary: state.data.reportsSummary,
@@ -4518,23 +5407,21 @@ var WorkspaceTabBar = ({ activeTab, showBrand = false, billingSummary }) => {
4518
5407
  ) : null,
4519
5408
  /* @__PURE__ */ jsx9(Box8, { flexDirection: "row", gap: 0, flexWrap: "wrap", children: workspaceTabs.map((tab) => {
4520
5409
  const active = tab === activeTab;
5410
+ const style = workspaceTabButtonStyle(active);
4521
5411
  return /* @__PURE__ */ jsx9(
4522
5412
  Box8,
4523
5413
  {
4524
- borderStyle: active ? "bold" : raisedButtonStyle.border,
4525
- borderColor: active ? terminalTheme.focus : terminalTheme.muted,
4526
- paddingX: 1,
5414
+ borderStyle: style.borderStyle,
5415
+ borderColor: style.borderColor,
4527
5416
  marginRight: 1,
4528
- children: /* @__PURE__ */ jsxs7(
5417
+ children: /* @__PURE__ */ jsx9(
4529
5418
  Text9,
4530
5419
  {
4531
- bold: active,
4532
- color: active ? terminalTheme.focus : void 0,
4533
- children: [
4534
- active ? raisedButtonStyle.activeMarker : raisedButtonStyle.inactiveMarker,
4535
- " ",
4536
- workspaceTabButtonLabel(tab)
4537
- ]
5420
+ bold: style.bold,
5421
+ color: style.color,
5422
+ backgroundColor: style.backgroundColor,
5423
+ underline: style.underline,
5424
+ children: workspaceTabButtonInterior(tab, active)
4538
5425
  }
4539
5426
  )
4540
5427
  },
@@ -4550,7 +5437,7 @@ var WorkspacePanel = (props) => {
4550
5437
  const state = props.state;
4551
5438
  const animate = props.animate ?? true;
4552
5439
  const framed = props.framed ?? true;
4553
- const connections = directArray2(state.data.connections);
5440
+ const connections = directArray3(state.data.connections);
4554
5441
  const isInitialLoading = state.status === "loading";
4555
5442
  const isBackgroundRefreshing = Boolean(
4556
5443
  state.isRefreshing && !isInitialLoading
@@ -4562,7 +5449,7 @@ var WorkspacePanel = (props) => {
4562
5449
  ] }) }) : null,
4563
5450
  isInitialLoading ? /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
4564
5451
  /* @__PURE__ */ jsx9(Spinner, { type: "dots", animate }),
4565
- /* @__PURE__ */ jsx9(Text9, { color: terminalTheme.brand, children: "Loading real API data..." })
5452
+ /* @__PURE__ */ jsx9(Text9, { color: terminalTheme.brand, children: "Loading workspace data..." })
4566
5453
  ] }) : null,
4567
5454
  isBackgroundRefreshing ? /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
4568
5455
  /* @__PURE__ */ jsx9(Spinner, { type: "dots", animate }),
@@ -4626,10 +5513,23 @@ var WorkspacePanel = (props) => {
4626
5513
  ProjectsView,
4627
5514
  {
4628
5515
  projects: props.projects,
4629
- selectedProject: props.selectedProject
5516
+ selectedProject: props.selectedProject,
5517
+ connections,
5518
+ reportsSummary: state.data.reportsSummary,
5519
+ compact,
5520
+ terminalColumns: props.terminalColumns
5521
+ }
5522
+ ) : null,
5523
+ props.tab === "connections" ? /* @__PURE__ */ jsx9(
5524
+ ConnectionsView,
5525
+ {
5526
+ connections,
5527
+ projects: props.projects,
5528
+ selectedIndex: props.selectedConnectionIndex ?? 0,
5529
+ compact,
5530
+ terminalColumns: props.terminalColumns
4630
5531
  }
4631
5532
  ) : null,
4632
- props.tab === "connections" ? /* @__PURE__ */ jsx9(ConnectionsView, { connections }) : null,
4633
5533
  props.tab === "billing" ? /* @__PURE__ */ jsx9(
4634
5534
  BillingView,
4635
5535
  {
@@ -4658,6 +5558,20 @@ var WorkspacePanel = (props) => {
4658
5558
  );
4659
5559
  };
4660
5560
 
5561
+ // src/ui/workspaceSelection.ts
5562
+ var selectableWorkspaceTab = (tab) => tab === "projects" || tab === "connections";
5563
+ var nextWorkspaceSelectionIndex = ({
5564
+ currentIndex,
5565
+ itemCount,
5566
+ direction
5567
+ }) => {
5568
+ if (itemCount <= 0) {
5569
+ return 0;
5570
+ }
5571
+ const normalizedCurrent = Math.min(Math.max(0, currentIndex), itemCount - 1);
5572
+ return Math.min(Math.max(0, normalizedCurrent + direction), itemCount - 1);
5573
+ };
5574
+
4661
5575
  // src/ui/animationPolicy.ts
4662
5576
  var truthyEnv = (value) => value !== void 0 && /^(1|true|yes|on)$/i.test(value);
4663
5577
  var shouldEnableTuiAnimations = ({
@@ -4671,7 +5585,7 @@ var shouldEnableTuiAnimations = ({
4671
5585
  };
4672
5586
 
4673
5587
  // src/ui/App.tsx
4674
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
5588
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
4675
5589
  var bootSteps = [
4676
5590
  "Loading config",
4677
5591
  "Validating auth",
@@ -4679,10 +5593,11 @@ var bootSteps = [
4679
5593
  "Ready"
4680
5594
  ];
4681
5595
  var defaultUser = { id: "cli-user", name: "CLI User" };
5596
+ var newDraftChatSessionKey = () => `draft-${randomUUID2()}`;
4682
5597
  var getUserNameFromToken = async (token) => {
4683
5598
  if (!token) return "You";
4684
5599
  try {
4685
- const { extractEmailFromToken } = await import("./dist-TBAQ5KOK.js");
5600
+ const { extractEmailFromToken } = await import("./dist-AGQQPJUD.js");
4686
5601
  const email = extractEmailFromToken(token);
4687
5602
  return getFirstNameForDisplay({ email: email ?? void 0 });
4688
5603
  } catch {
@@ -4700,9 +5615,9 @@ var dropdownIndicator = "\u25BE";
4700
5615
  var bundledAgentProfiles = getBundledAgentProfiles();
4701
5616
  var agentProfileItems = [
4702
5617
  {
4703
- label: "Profile",
5618
+ label: "General",
4704
5619
  value: "",
4705
- description: "Default CloudEval chat flow without an Agent Profile."
5620
+ description: "Built-in CloudEval chat flow without a named Agent Profile."
4706
5621
  },
4707
5622
  ...bundledAgentProfiles.map((profile) => ({
4708
5623
  label: profile.display_name,
@@ -4790,11 +5705,27 @@ var billingHeaderFromEntitlement = (entitlement, usageSummary) => {
4790
5705
  plan: creditStatus.planName,
4791
5706
  remaining: creditStatus.remaining,
4792
5707
  total: creditStatus.total,
5708
+ used: creditStatus.used,
5709
+ reportedUsed: creditStatus.reportedUsed,
4793
5710
  tone: creditStatus.tone,
4794
5711
  status: entitlement?.effective_status
4795
5712
  };
4796
5713
  };
4797
5714
  var isBusyStatus = (status) => status === "connecting" || status === "thinking" || status === "streaming" || status === "tool_running" || status === "hitl_waiting";
5715
+ var buildTuiHeaderDetails = ({
5716
+ apiBase,
5717
+ frontendBaseUrl,
5718
+ billingSummary,
5719
+ userName
5720
+ }) => {
5721
+ const displayName = truncateForTerminal(userName.trim() || "You", 64);
5722
+ return [
5723
+ `User: ${displayName}`,
5724
+ `API: ${apiBase}`,
5725
+ `Frontend: ${frontendBaseUrl}`,
5726
+ billingSummary
5727
+ ];
5728
+ };
4798
5729
  var isTerminalThinkingStatus = (status) => status === "completed" || status === "error" || status === "aborted" || status === "cancelled";
4799
5730
  var hasCancellableAssistantWork = (messages) => messages.some(
4800
5731
  (message) => message.role === "assistant" && (message.pending || message.thinkingSteps?.some((step) => !isTerminalThinkingStatus(step.status ?? "streaming")))
@@ -4890,6 +5821,35 @@ var openExternalUrl = (url) => {
4890
5821
  });
4891
5822
  child.unref();
4892
5823
  };
5824
+ var directArray4 = (value) => {
5825
+ if (Array.isArray(value)) {
5826
+ return value;
5827
+ }
5828
+ if (!value || typeof value !== "object") {
5829
+ return [];
5830
+ }
5831
+ const record = value;
5832
+ for (const key of ["data", "items", "rows", "results", "connections", "ledger", "invoices", "notifications", "topups"]) {
5833
+ const candidate = record[key];
5834
+ if (Array.isArray(candidate)) {
5835
+ return candidate;
5836
+ }
5837
+ }
5838
+ return [];
5839
+ };
5840
+ var recordLabel = (value, fallback) => {
5841
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5842
+ return fallback;
5843
+ }
5844
+ const record = value;
5845
+ for (const key of ["name", "title", "id"]) {
5846
+ const current = record[key];
5847
+ if (typeof current === "string" && current.trim()) {
5848
+ return current.trim();
5849
+ }
5850
+ }
5851
+ return fallback;
5852
+ };
4893
5853
  var writeTableDownload = ({
4894
5854
  tab,
4895
5855
  data,
@@ -4897,27 +5857,11 @@ var writeTableDownload = ({
4897
5857
  selectedProject,
4898
5858
  tablePage
4899
5859
  }) => {
4900
- const directArray3 = (value) => {
4901
- if (Array.isArray(value)) {
4902
- return value;
4903
- }
4904
- if (!value || typeof value !== "object") {
4905
- return [];
4906
- }
4907
- const record = value;
4908
- for (const key of ["data", "items", "rows", "results", "connections", "ledger", "invoices", "notifications", "topups"]) {
4909
- const candidate = record[key];
4910
- if (Array.isArray(candidate)) {
4911
- return candidate;
4912
- }
4913
- }
4914
- return [];
4915
- };
4916
5860
  const derived = tab === "overview" ? buildOverviewDashboardModel({
4917
5861
  dashboard: data.dashboard,
4918
5862
  reportsSummary: data.reportsSummary,
4919
5863
  fallbackProjectCount: projects.length,
4920
- fallbackConnectionCount: directArray3(data.connections).length
5864
+ fallbackConnectionCount: directArray4(data.connections).length
4921
5865
  }) : tab === "reports" ? buildReportsDashboardModel({
4922
5866
  dashboard: data.dashboard,
4923
5867
  reportsSummary: data.reportsSummary,
@@ -4926,18 +5870,18 @@ var writeTableDownload = ({
4926
5870
  wafReport: data.wafReport
4927
5871
  }) : {
4928
5872
  rows: {
4929
- connections: directArray3(data.connections),
4930
- ledger: directArray3(data.ledger),
4931
- invoices: directArray3(data.billingInfo),
4932
- topups: directArray3(data.topups),
4933
- notifications: directArray3(data.notifications)
5873
+ connections: directArray4(data.connections),
5874
+ ledger: directArray4(data.ledger),
5875
+ invoices: directArray4(data.billingInfo),
5876
+ topups: directArray4(data.topups),
5877
+ notifications: directArray4(data.notifications)
4934
5878
  }
4935
5879
  };
4936
- const dir = resolve(process.cwd(), ".cloudeval-downloads");
4937
- mkdirSync(dir, { recursive: true });
5880
+ const dir = resolve2(process.cwd(), ".cloudeval-downloads");
5881
+ mkdirSync2(dir, { recursive: true });
4938
5882
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4939
- const file = join(dir, `${tab}-tables-${timestamp}.json`);
4940
- writeFileSync(
5883
+ const file = join2(dir, `${tab}-tables-${timestamp}.json`);
5884
+ writeFileSync2(
4941
5885
  file,
4942
5886
  `${JSON.stringify(
4943
5887
  {
@@ -4980,12 +5924,17 @@ var estimatePromptPanelRows = ({
4980
5924
  suggestionRows,
4981
5925
  compact,
4982
5926
  hasThinkingSteps,
4983
- includeControls = true
5927
+ includeControls = true,
5928
+ variant = "panel"
4984
5929
  }) => {
4985
- const outerChromeRows = 2;
4986
- const inputRowsWithHint = inputRows + 1;
4987
- const footerRows = includeControls ? 1 + estimatePromptControlRows({ compact, hasThinkingSteps }) : 0;
4988
- return outerChromeRows + suggestionRows + inputRowsWithHint + footerRows;
5930
+ const controlRows = includeControls ? 1 + estimatePromptControlRows({ compact, hasThinkingSteps }) : 0;
5931
+ return estimateComposerRows({
5932
+ inputRows,
5933
+ suggestionRows,
5934
+ includeControls,
5935
+ controlRows,
5936
+ variant
5937
+ });
4989
5938
  };
4990
5939
  var useTerminalSize = () => {
4991
5940
  const [size, setSize] = useState4(() => readTerminalSize());
@@ -5129,6 +6078,24 @@ var QueuePanel = ({ messages, compact, terminalColumns }) => {
5129
6078
  }
5130
6079
  );
5131
6080
  };
6081
+ var artifactToneColor = (tone) => {
6082
+ if (tone === "brand") return terminalTheme.brand;
6083
+ if (tone === "success") return terminalTheme.success;
6084
+ if (tone === "warning") return terminalTheme.warning;
6085
+ if (tone === "danger") return terminalTheme.danger;
6086
+ return void 0;
6087
+ };
6088
+ var ArtifactStrip = ({ chips, compact = false, terminalColumns }) => {
6089
+ if (!chips.length) {
6090
+ return null;
6091
+ }
6092
+ const valueLimit = compact ? Math.max(10, terminalColumns - 15) : 24;
6093
+ return /* @__PURE__ */ jsx10(Box9, { flexDirection: compact ? "column" : "row", gap: compact ? 0 : 1, flexWrap: "wrap", children: chips.map((chip) => /* @__PURE__ */ jsxs8(Text10, { children: [
6094
+ /* @__PURE__ */ jsx10(Text10, { color: artifactToneColor(chip.tone), bold: true, children: chip.label }),
6095
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " " }),
6096
+ /* @__PURE__ */ jsx10(Text10, { color: artifactToneColor(chip.tone), children: truncateForTerminal(chip.value, valueLimit) })
6097
+ ] }, `${chip.label}-${chip.value}`)) });
6098
+ };
5132
6099
  var PromptControlBar = ({
5133
6100
  focused,
5134
6101
  selectedThreadTitle,
@@ -5178,7 +6145,8 @@ var PromptControlBar = ({
5178
6145
  return /* @__PURE__ */ jsxs8(
5179
6146
  Text10,
5180
6147
  {
5181
- color: isFocused ? terminalTheme.focus : void 0,
6148
+ color: isFocused ? "white" : void 0,
6149
+ backgroundColor: isFocused ? terminalTheme.selectedBackground : void 0,
5182
6150
  bold: isFocused,
5183
6151
  wrap: "truncate",
5184
6152
  children: [
@@ -5203,7 +6171,8 @@ var PromptControlBar = ({
5203
6171
  hasThinkingSteps ? /* @__PURE__ */ jsxs8(
5204
6172
  Text10,
5205
6173
  {
5206
- color: focused === "thinking" ? terminalTheme.focus : void 0,
6174
+ color: focused === "thinking" ? "white" : void 0,
6175
+ backgroundColor: focused === "thinking" ? terminalTheme.selectedBackground : void 0,
5207
6176
  bold: focused === "thinking",
5208
6177
  wrap: "truncate",
5209
6178
  children: [
@@ -5222,7 +6191,7 @@ var PromptControlBar = ({
5222
6191
  ] });
5223
6192
  }
5224
6193
  return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 0, children: [
5225
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Settings" }),
6194
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Command context" }),
5226
6195
  /* @__PURE__ */ jsxs8(Box9, { flexDirection: compact ? "column" : "row", gap: controlGap, flexWrap: "wrap", children: [
5227
6196
  selectorOrder.map((kind) => {
5228
6197
  const isFocused = focused === kind;
@@ -5243,15 +6212,23 @@ var PromptControlBar = ({
5243
6212
  borderStyle: raisedButtonStyle.border,
5244
6213
  borderColor: isFocused ? terminalTheme.focus : terminalTheme.muted,
5245
6214
  paddingX: 1,
5246
- children: /* @__PURE__ */ jsxs8(Text10, { color: isFocused ? terminalTheme.focus : void 0, bold: isFocused, children: [
5247
- isFocused ? raisedButtonStyle.activeMarker : raisedButtonStyle.inactiveMarker,
5248
- " ",
5249
- label,
5250
- " [",
5251
- value,
5252
- "] ",
5253
- dropdownIndicator
5254
- ] })
6215
+ children: /* @__PURE__ */ jsxs8(
6216
+ Text10,
6217
+ {
6218
+ color: isFocused ? "white" : void 0,
6219
+ backgroundColor: isFocused ? terminalTheme.selectedBackground : void 0,
6220
+ bold: isFocused,
6221
+ children: [
6222
+ isFocused ? raisedButtonStyle.activeMarker : raisedButtonStyle.inactiveMarker,
6223
+ " ",
6224
+ label,
6225
+ " [",
6226
+ value,
6227
+ "] ",
6228
+ dropdownIndicator
6229
+ ]
6230
+ }
6231
+ )
5255
6232
  },
5256
6233
  kind
5257
6234
  );
@@ -5265,7 +6242,8 @@ var PromptControlBar = ({
5265
6242
  children: /* @__PURE__ */ jsxs8(
5266
6243
  Text10,
5267
6244
  {
5268
- color: focused === "thinking" ? terminalTheme.focus : void 0,
6245
+ color: focused === "thinking" ? "white" : void 0,
6246
+ backgroundColor: focused === "thinking" ? terminalTheme.selectedBackground : void 0,
5269
6247
  bold: focused === "thinking",
5270
6248
  children: [
5271
6249
  focused === "thinking" ? raisedButtonStyle.activeMarker : raisedButtonStyle.inactiveMarker,
@@ -5287,8 +6265,10 @@ var PromptControlBar = ({
5287
6265
  var ChatContextPanel = ({
5288
6266
  width,
5289
6267
  height,
6268
+ mode,
5290
6269
  active,
5291
6270
  focused,
6271
+ artifactChips,
5292
6272
  selectedProject,
5293
6273
  selectedThreadTitle,
5294
6274
  selectedModel,
@@ -5302,50 +6282,82 @@ var ChatContextPanel = ({
5302
6282
  busy,
5303
6283
  animate
5304
6284
  }) => {
5305
- return /* @__PURE__ */ jsx10(
6285
+ return /* @__PURE__ */ jsxs8(
5306
6286
  TitledBox,
5307
6287
  {
5308
- title: "Settings",
6288
+ title: "Context",
5309
6289
  borderStyle: "round",
5310
6290
  borderColor: active ? terminalTheme.focus : terminalTheme.muted,
5311
- padding: 1,
6291
+ padding: mode === "medium" ? 0 : 1,
6292
+ paddingX: 1,
5312
6293
  width,
5313
6294
  height,
5314
- children: /* @__PURE__ */ jsx10(
5315
- PromptControlBar,
5316
- {
5317
- focused,
5318
- selectedThreadTitle,
5319
- selectedProject,
5320
- selectedModel,
5321
- selectedMode,
5322
- selectedAgentProfileLabel,
5323
- hasThinkingSteps,
5324
- thinkingExpanded,
5325
- thinkingSummary,
5326
- compact: true,
5327
- terminalColumns: Math.max(24, width - 4),
5328
- statusText,
5329
- statusColor,
5330
- busy,
5331
- animate
5332
- }
5333
- )
6295
+ children: [
6296
+ artifactChips.length ? /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginBottom: 1, children: [
6297
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.brand, children: "Artifacts" }),
6298
+ /* @__PURE__ */ jsx10(
6299
+ ArtifactStrip,
6300
+ {
6301
+ chips: artifactChips,
6302
+ compact: true,
6303
+ terminalColumns: Math.max(24, width - 4)
6304
+ }
6305
+ )
6306
+ ] }) : null,
6307
+ /* @__PURE__ */ jsx10(
6308
+ PromptControlBar,
6309
+ {
6310
+ focused,
6311
+ selectedThreadTitle,
6312
+ selectedProject,
6313
+ selectedModel,
6314
+ selectedMode,
6315
+ selectedAgentProfileLabel,
6316
+ hasThinkingSteps,
6317
+ thinkingExpanded,
6318
+ thinkingSummary,
6319
+ compact: true,
6320
+ terminalColumns: Math.max(24, width - 4),
6321
+ statusText,
6322
+ statusColor,
6323
+ busy,
6324
+ animate
6325
+ }
6326
+ )
6327
+ ]
5334
6328
  }
5335
6329
  );
5336
6330
  };
5337
6331
  var BottomControls = ({ tab }) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", children: [
5338
6332
  /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
5339
- /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.brand, bold: true, children: "Keys" }),
5340
- /* @__PURE__ */ jsx10(Text10, { children: " \u2191/\u2193 move or scroll | PgUp/PgDn scroll | [/] table page | " }),
5341
- /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.brand, bold: true, children: "D" }),
5342
- /* @__PURE__ */ jsx10(Text10, { children: " download | " }),
5343
- /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.brand, bold: true, children: "R" }),
5344
- /* @__PURE__ */ jsx10(Text10, { children: " refresh | " }),
5345
- /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.brand, bold: true, children: "O" }),
5346
- /* @__PURE__ */ jsx10(Text10, { children: " open | " }),
5347
- /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.brand, bold: true, children: "C" }),
5348
- /* @__PURE__ */ jsx10(Text10, { children: " chat" })
6333
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "\u2303Q" }),
6334
+ /* @__PURE__ */ jsx10(Text10, { children: " Quit " }),
6335
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "\u2303T" }),
6336
+ /* @__PURE__ */ jsx10(Text10, { children: " Tabs " }),
6337
+ tab === "chat" ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
6338
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "Y" }),
6339
+ /* @__PURE__ */ jsx10(Text10, { children: " Copy " }),
6340
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "D" }),
6341
+ /* @__PURE__ */ jsx10(Text10, { children: " Download " }),
6342
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "O" }),
6343
+ /* @__PURE__ */ jsx10(Text10, { children: " Open " }),
6344
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.brand, children: "/ Commands" })
6345
+ ] }) : /* @__PURE__ */ jsxs8(Fragment3, { children: [
6346
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "R" }),
6347
+ /* @__PURE__ */ jsx10(Text10, { children: " Refresh " }),
6348
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "O" }),
6349
+ /* @__PURE__ */ jsx10(Text10, { children: " Open " }),
6350
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "D" }),
6351
+ /* @__PURE__ */ jsx10(Text10, { children: " Download " }),
6352
+ tab === "projects" || tab === "connections" ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
6353
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "\u2191\u2193/J/K" }),
6354
+ /* @__PURE__ */ jsx10(Text10, { children: " Select " }),
6355
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "Enter" }),
6356
+ /* @__PURE__ */ jsx10(Text10, { children: " Confirm " })
6357
+ ] }) : null,
6358
+ /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.accent, bold: true, children: "[/]" }),
6359
+ /* @__PURE__ */ jsx10(Text10, { children: " Page" })
6360
+ ] })
5349
6361
  ] }),
5350
6362
  tab === "reports" ? /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
5351
6363
  /* @__PURE__ */ jsx10(Text10, { color: terminalTheme.brand, bold: true, children: "Reports" }),
@@ -5443,6 +6455,10 @@ var App = ({
5443
6455
  threadId: conversationId,
5444
6456
  debug
5445
6457
  });
6458
+ const [activeDraftSessionKey, setActiveDraftSessionKey] = useState4(
6459
+ () => conversationId ? void 0 : newDraftChatSessionKey()
6460
+ );
6461
+ const [draftChatSessions, setDraftChatSessions] = useState4({});
5446
6462
  const [projects, setProjects] = useState4([]);
5447
6463
  const [selectedProject, setSelectedProject] = useState4(null);
5448
6464
  const [selectedModel, setSelectedModel] = useState4(model ?? "");
@@ -5455,6 +6471,7 @@ var App = ({
5455
6471
  createWorkspacePanelStateMap
5456
6472
  );
5457
6473
  const [tablePageByTab, setTablePageByTab] = useState4({});
6474
+ const [selectedConnectionIndex, setSelectedConnectionIndex] = useState4(0);
5458
6475
  const [workspaceRefreshKeys, setWorkspaceRefreshKeys] = useState4({});
5459
6476
  const [workspaceStaleTick, setWorkspaceStaleTick] = useState4(0);
5460
6477
  const [modelItems, setModelItems] = useState4(() => {
@@ -5485,6 +6502,7 @@ var App = ({
5485
6502
  const [scrollOffset, setScrollOffset] = useState4(0);
5486
6503
  const [contentHeight, setContentHeight] = useState4(0);
5487
6504
  const [viewportHeight, setViewportHeight] = useState4(20);
6505
+ const [slashCommandCompletionIndex, setSlashCommandCompletionIndex] = useState4(0);
5488
6506
  const [workspaceScrollOffset, setWorkspaceScrollOffset] = useState4(0);
5489
6507
  const [workspaceContentHeight, setWorkspaceContentHeight] = useState4(0);
5490
6508
  const [workspaceViewportHeight, setWorkspaceViewportHeight] = useState4(14);
@@ -5500,7 +6518,7 @@ var App = ({
5500
6518
  () => bundledAgentProfiles.find((profile) => profile.id === selectedAgentProfileId),
5501
6519
  [selectedAgentProfileId]
5502
6520
  );
5503
- const selectedAgentProfileLabel = selectedAgentProfile?.display_name ?? "Profile";
6521
+ const selectedAgentProfileLabel = selectedAgentProfile?.display_name ?? "General";
5504
6522
  const frontendThreadUrl = useMemo(
5505
6523
  () => buildFrontendThreadUrl(frontendBaseUrl, chatState.threadId),
5506
6524
  [frontendBaseUrl, chatState.threadId]
@@ -5555,6 +6573,17 @@ var App = ({
5555
6573
  () => remoteThreads.find((thread) => thread.thread_id === chatState.threadId),
5556
6574
  [chatState.threadId, remoteThreads]
5557
6575
  );
6576
+ const draftThreadSummaries = useMemo(
6577
+ () => Object.values(draftChatSessions).map(
6578
+ (draft) => buildDraftThreadSummary({
6579
+ key: draft.key,
6580
+ state: draft.state,
6581
+ projectName: draft.projectName,
6582
+ updatedAt: draft.updatedAt
6583
+ })
6584
+ ).filter((draft) => Boolean(draft)),
6585
+ [draftChatSessions]
6586
+ );
5558
6587
  const selectedThreadTitle = useMemo(
5559
6588
  () => threadPanelTitle({
5560
6589
  session: selectedLocalSession,
@@ -5565,8 +6594,10 @@ var App = ({
5565
6594
  [chatState.messages.length, chatState.threadId, selectedLocalSession, selectedRemoteThread]
5566
6595
  );
5567
6596
  const threadSelectItems = useMemo(
5568
- () => buildThreadSelectItems(localSessions, chatState.threadId, remoteThreads),
5569
- [chatState.threadId, localSessions, remoteThreads]
6597
+ () => buildThreadSelectItems(localSessions, chatState.threadId, remoteThreads, {
6598
+ drafts: draftThreadSummaries
6599
+ }),
6600
+ [chatState.threadId, draftThreadSummaries, localSessions, remoteThreads]
5570
6601
  );
5571
6602
  const checkHealth = useMemo(() => {
5572
6603
  return async (token) => {
@@ -5602,7 +6633,7 @@ var App = ({
5602
6633
  modes: modeItems,
5603
6634
  threads: threadSelectItems.map((item) => ({
5604
6635
  label: item.label,
5605
- value: item.value.kind === "remote" ? item.value.thread.thread_id : item.value.kind === "session" ? item.value.session.threadId : "new",
6636
+ value: item.value.kind === "remote" ? item.value.thread.thread_id : item.value.kind === "session" ? item.value.session.threadId : item.value.kind === "draft" ? item.value.draft.key : "new",
5606
6637
  description: item.description
5607
6638
  })),
5608
6639
  profiles: agentProfileItems
@@ -5691,6 +6722,7 @@ var App = ({
5691
6722
  threadId: session.threadId,
5692
6723
  messages
5693
6724
  }));
6725
+ setActiveDraftSessionKey(void 0);
5694
6726
  if (session.model) {
5695
6727
  setSelectedModel(session.model);
5696
6728
  }
@@ -5738,6 +6770,7 @@ var App = ({
5738
6770
  threadId,
5739
6771
  messages
5740
6772
  }));
6773
+ setActiveDraftSessionKey(void 0);
5741
6774
  const nextModel = history.model || history.thread_head?.model || thread.model;
5742
6775
  if (nextModel) {
5743
6776
  setSelectedModel(nextModel);
@@ -5764,11 +6797,33 @@ var App = ({
5764
6797
  },
5765
6798
  [loadRemoteThreadHistory, projects]
5766
6799
  );
6800
+ const restoreDraftThread = React8.useCallback((key) => {
6801
+ const draft = draftChatSessions[key];
6802
+ if (!draft) {
6803
+ setNotice("Open session was not found.");
6804
+ return;
6805
+ }
6806
+ if (isBusyStatus(chatState.status) || controllerRef.current) {
6807
+ setNotice("Stop the running response before switching sessions.");
6808
+ return;
6809
+ }
6810
+ setActiveDraftSessionKey(key);
6811
+ setChatState((prev) => ({
6812
+ ...draft.state,
6813
+ debug: prev.debug
6814
+ }));
6815
+ setActiveSelector(null);
6816
+ autoExpandedThinkingMessageIdsRef.current.clear();
6817
+ setExpandedThinkingMessageIds(/* @__PURE__ */ new Set());
6818
+ setNotice(`Loaded open session: ${truncateForTerminal(draft.state.threadId ?? key, 90)}`);
6819
+ setTimeout(() => scrollViewRef.current?.scrollToBottom(), 0);
6820
+ }, [chatState.status, draftChatSessions]);
5767
6821
  const startNewThread = React8.useCallback(() => {
5768
6822
  if (isBusyStatus(chatState.status) || controllerRef.current) {
5769
- setNotice("Stop the running response before switching threads.");
6823
+ setNotice("Stop the running response before starting another session.");
5770
6824
  return;
5771
6825
  }
6826
+ setActiveDraftSessionKey(newDraftChatSessionKey());
5772
6827
  queueRef.current = [];
5773
6828
  setQueuedMessages([]);
5774
6829
  setChatState((prev) => ({
@@ -5792,7 +6847,11 @@ var App = ({
5792
6847
  return;
5793
6848
  }
5794
6849
  if (isBusyStatus(chatState.status) || controllerRef.current) {
5795
- setNotice("Stop the running response before switching threads.");
6850
+ setNotice("Stop the running response before switching sessions.");
6851
+ return;
6852
+ }
6853
+ if (choice.kind === "draft") {
6854
+ restoreDraftThread(choice.draft.key);
5796
6855
  return;
5797
6856
  }
5798
6857
  if (choice.kind === "remote") {
@@ -5803,7 +6862,7 @@ var App = ({
5803
6862
  }
5804
6863
  restoreLocalSession(choice.session);
5805
6864
  },
5806
- [chatState.status, restoreLocalSession, restoreRemoteThread, startNewThread]
6865
+ [chatState.status, restoreDraftThread, restoreLocalSession, restoreRemoteThread, startNewThread]
5807
6866
  );
5808
6867
  const selectProjectForUser = async (token, user) => {
5809
6868
  setLoadingProjects(true);
@@ -5844,6 +6903,24 @@ var App = ({
5844
6903
  controllerRef.current?.abort("App unmounted");
5845
6904
  };
5846
6905
  }, []);
6906
+ useEffect4(() => {
6907
+ if (!activeDraftSessionKey) {
6908
+ return;
6909
+ }
6910
+ const hasLocalContent = Boolean(chatState.threadId) || chatState.messages.some((message) => message.content.trim());
6911
+ if (!hasLocalContent) {
6912
+ return;
6913
+ }
6914
+ setDraftChatSessions((current) => ({
6915
+ ...current,
6916
+ [activeDraftSessionKey]: {
6917
+ key: activeDraftSessionKey,
6918
+ state: chatState,
6919
+ projectName: selectedProject?.name,
6920
+ updatedAt: Date.now()
6921
+ }
6922
+ }));
6923
+ }, [activeDraftSessionKey, chatState, selectedProject?.name]);
5847
6924
  useEffect4(() => {
5848
6925
  if (phase !== "ready") {
5849
6926
  return;
@@ -5964,7 +7041,7 @@ var App = ({
5964
7041
  setIsLoggingIn(true);
5965
7042
  setLoaderStep(1);
5966
7043
  try {
5967
- const { login } = await import("./dist-TBAQ5KOK.js");
7044
+ const { login } = await import("./dist-AGQQPJUD.js");
5968
7045
  const newToken = await login(baseUrl, {
5969
7046
  headless: Boolean(process.env.SSH_TTY || process.env.CI)
5970
7047
  });
@@ -6095,7 +7172,7 @@ var App = ({
6095
7172
  if (!loadReason) {
6096
7173
  return;
6097
7174
  }
6098
- if (tab === "chat" || tab === "projects" || tab === "options" || tab === "help") {
7175
+ if (tab === "chat" || tab === "options" || tab === "help") {
6099
7176
  const now = Date.now();
6100
7177
  setWorkspacePanelTabState(tab, (previous) => ({
6101
7178
  ...previous,
@@ -6470,7 +7547,36 @@ var App = ({
6470
7547
  setNotice(`Profile selected: ${label}. Mode set to Agent.`);
6471
7548
  return;
6472
7549
  }
6473
- setNotice("Agent Profile cleared. Default chat flow active.");
7550
+ setNotice("Agent Profile cleared. General chat flow active.");
7551
+ };
7552
+ const copyLatestAssistantResponse = () => {
7553
+ const text = buildLatestAssistantResponseText(chatState.messages);
7554
+ if (!text) {
7555
+ setNotice("No assistant response to copy yet.");
7556
+ return;
7557
+ }
7558
+ try {
7559
+ copyTextToClipboard(text);
7560
+ setNotice("Copied latest response with numbered citations.");
7561
+ } catch (error) {
7562
+ setNotice(`Copy failed: ${error?.message ?? "clipboard unavailable"}`);
7563
+ }
7564
+ };
7565
+ const downloadChatTranscript = () => {
7566
+ if (!chatState.messages.some((message) => message.content.trim())) {
7567
+ setNotice("No chat transcript to download yet.");
7568
+ return;
7569
+ }
7570
+ try {
7571
+ const file = writeChatTranscriptDownload({
7572
+ messages: chatState.messages,
7573
+ userName,
7574
+ threadId: chatState.threadId
7575
+ });
7576
+ setNotice(`Downloaded chat transcript: ${file}`);
7577
+ } catch (error) {
7578
+ setNotice(`Failed to download chat transcript: ${error?.message ?? "Unknown error"}`);
7579
+ }
6474
7580
  };
6475
7581
  const handlePromptSubmit = (value) => {
6476
7582
  const cleanedValue = sanitizeTerminalMultilineInput(value);
@@ -6498,6 +7604,13 @@ var App = ({
6498
7604
  selectThread({ kind: "remote", thread: remoteThread });
6499
7605
  return;
6500
7606
  }
7607
+ const draftThread = draftThreadSummaries.find(
7608
+ (candidate) => candidate.key === promptCommand.threadId
7609
+ );
7610
+ if (draftThread) {
7611
+ selectThread({ kind: "draft", draft: draftThread });
7612
+ return;
7613
+ }
6501
7614
  const session = localSessions.find(
6502
7615
  (candidate) => candidate.threadId === promptCommand.threadId
6503
7616
  );
@@ -6533,6 +7646,12 @@ var App = ({
6533
7646
  case "openFrontend":
6534
7647
  handleOpenFrontend();
6535
7648
  return;
7649
+ case "copyLatestResponse":
7650
+ copyLatestAssistantResponse();
7651
+ return;
7652
+ case "downloadTranscript":
7653
+ downloadChatTranscript();
7654
+ return;
6536
7655
  case "showHelp":
6537
7656
  setActiveWorkspaceTab("help");
6538
7657
  setNotice(void 0);
@@ -6608,6 +7727,7 @@ var App = ({
6608
7727
  return;
6609
7728
  }
6610
7729
  }
7730
+ const draftKeyAtStart = activeDraftSessionKey;
6611
7731
  const threadId = chatState.threadId ?? randomUUID2();
6612
7732
  const now = Date.now();
6613
7733
  const userMessage = options.hitlResume || options.queuedMessageId ? void 0 : {
@@ -6765,6 +7885,16 @@ var App = ({
6765
7885
  model: selectedModel,
6766
7886
  profile: configProfile
6767
7887
  });
7888
+ if (draftKeyAtStart) {
7889
+ setDraftChatSessions((current) => {
7890
+ const next = { ...current };
7891
+ delete next[draftKeyAtStart];
7892
+ return next;
7893
+ });
7894
+ setActiveDraftSessionKey(
7895
+ (current) => current === draftKeyAtStart ? void 0 : current
7896
+ );
7897
+ }
6768
7898
  void refreshLocalSessions().catch(() => void 0);
6769
7899
  void refreshRemoteThreads().catch(() => void 0);
6770
7900
  } catch (error) {
@@ -6830,9 +7960,20 @@ var App = ({
6830
7960
  0,
6831
7961
  terminalSize.columns < 110 ? 3 : 4
6832
7962
  );
7963
+ const slashCommandCompletions = useMemo(
7964
+ () => buildSlashCommandCompletionItems(input),
7965
+ [input]
7966
+ );
7967
+ const activeSlashCommandCompletionIndex = slashCommandCompletions.length ? Math.min(slashCommandCompletionIndex, slashCommandCompletions.length - 1) : void 0;
7968
+ const activeSlashCommandCompletion = activeSlashCommandCompletionIndex === void 0 ? void 0 : slashCommandCompletions[activeSlashCommandCompletionIndex];
7969
+ const slashCommandCompletionsActive = promptInputActive && activeWorkspaceTab === "chat" && !activeSelector && !isSearching && slashCommandCompletions.length > 0;
6833
7970
  const promptGhostSuffix = useMemo(() => {
6834
7971
  const trimmedStart = input.trimStart();
6835
7972
  if (trimmedStart.startsWith("/")) {
7973
+ const commandGhost = slashCommandGhostSuffix(input, activeSlashCommandCompletion);
7974
+ if (commandGhost) {
7975
+ return commandGhost;
7976
+ }
6836
7977
  return completePromptInput(input, promptCompletionContext)?.ghostSuffix;
6837
7978
  }
6838
7979
  for (const suggestion of visiblePromptSuggestions) {
@@ -6844,7 +7985,7 @@ var App = ({
6844
7985
  return visiblePromptSuggestions[0];
6845
7986
  }
6846
7987
  return void 0;
6847
- }, [input, promptCompletionContext, visiblePromptSuggestions]);
7988
+ }, [activeSlashCommandCompletion, input, promptCompletionContext, visiblePromptSuggestions]);
6848
7989
  const promptInputWidth = Math.max(20, terminalSize.columns - 14);
6849
7990
  const promptInputRowBudget = getPromptInputRowBudget(terminalSize);
6850
7991
  const promptInputViewport = getInputViewport({
@@ -6855,11 +7996,12 @@ var App = ({
6855
7996
  scrollOffset: promptInputScrollOffset
6856
7997
  });
6857
7998
  const promptInputRows = promptInputViewport.visibleRowCount;
6858
- const promptSuggestionRows = estimatePromptSuggestionRows(
6859
- visiblePromptSuggestions.length
6860
- );
7999
+ const promptCommandCompletionRows = slashCommandCompletions.length ? 1 : 0;
8000
+ const promptSuggestionRows = estimatePromptSuggestionRows(visiblePromptSuggestions.length) + promptCommandCompletionRows;
8001
+ const chatResponsiveMode = getChatResponsiveMode(terminalSize);
6861
8002
  const splitChatLayout = shouldUseSplitChatLayout(terminalSize);
6862
8003
  const promptControlsCompact = terminalSize.columns < 96;
8004
+ const promptFooterControlsVisible = !slashCommandCompletionsActive;
6863
8005
  const focusedFollowUpIndex = focusFollowUpIndex(focusedControl);
6864
8006
  const activeChatPanel = chatPanelFocusForControl(focusedControl);
6865
8007
  const controlFocusOrder = buildControlFocusOrder({
@@ -6883,18 +8025,19 @@ var App = ({
6883
8025
  const bannerDisabled = bannerDisabledByConfig || !tuiLayout.showBanner;
6884
8026
  const bannerContentColumns = Math.max(1, terminalSize.columns - tuiLayout.paddingX * 2);
6885
8027
  const billingSummary = billingHeaderError ? `Plan: unavailable | Credits: unavailable` : billingSummaryText(billingHeader);
6886
- const headerDetails = [
6887
- `API: ${apiBase}`,
6888
- `Frontend: ${frontendBaseUrl}`,
6889
- billingSummary
6890
- ];
8028
+ const headerDetails = buildTuiHeaderDetails({
8029
+ apiBase,
8030
+ frontendBaseUrl,
8031
+ billingSummary,
8032
+ userName
8033
+ });
6891
8034
  const keyBindings = getTuiKeyBindings();
6892
8035
  const scrollHelp = mouseTrackingEnabled ? `${keyBindings.mouse} | wheel scroll` : keyBindings.scroll;
6893
8036
  const chatAvailableWidth = Math.max(
6894
8037
  24,
6895
8038
  terminalSize.columns - tuiLayout.paddingX * 2
6896
8039
  );
6897
- const chatContextPanelWidth = splitChatLayout ? Math.min(44, Math.max(34, Math.floor(chatAvailableWidth * 0.24))) : 0;
8040
+ const chatContextPanelWidth = splitChatLayout ? getContextRailWidth(terminalSize) : 0;
6898
8041
  const chatSplitGap = splitChatLayout ? 1 : 0;
6899
8042
  const chatThreadPanelWidth = splitChatLayout ? Math.max(60, chatAvailableWidth - chatContextPanelWidth - chatSplitGap) : void 0;
6900
8043
  const threadContentWidth = Math.max(
@@ -6902,6 +8045,92 @@ var App = ({
6902
8045
  splitChatLayout ? (chatThreadPanelWidth ?? chatAvailableWidth) - 8 : chatAvailableWidth - 8
6903
8046
  );
6904
8047
  const activeWorkspacePanelState = workspacePanelStore[activeWorkspaceTab] ?? createWorkspacePanelState(activeWorkspaceTab, "loading");
8048
+ const activeConnections = directArray4(activeWorkspacePanelState.data.connections);
8049
+ const activeProjectList = projects.length ? projects : [selectedProject ?? defaultProject];
8050
+ const selectedProjectIndex = Math.max(
8051
+ 0,
8052
+ activeProjectList.findIndex(
8053
+ (project) => project.id === (selectedProject ?? activeProjectList[0])?.id
8054
+ )
8055
+ );
8056
+ const moveSelectedProject = React8.useCallback(
8057
+ (direction) => {
8058
+ const nextIndex = nextWorkspaceSelectionIndex({
8059
+ currentIndex: selectedProjectIndex,
8060
+ itemCount: activeProjectList.length,
8061
+ direction
8062
+ });
8063
+ const project = activeProjectList[nextIndex];
8064
+ if (!project) {
8065
+ setNotice("No project rows are available to select.");
8066
+ return;
8067
+ }
8068
+ setSelectedProject(project);
8069
+ setNotice(`Project selected: ${truncateForTerminal(project.name, 80)}`);
8070
+ },
8071
+ [activeProjectList, selectedProjectIndex]
8072
+ );
8073
+ const moveSelectedConnection = React8.useCallback(
8074
+ (direction) => {
8075
+ setSelectedConnectionIndex((current) => {
8076
+ const next = nextWorkspaceSelectionIndex({
8077
+ currentIndex: current,
8078
+ itemCount: activeConnections.length,
8079
+ direction
8080
+ });
8081
+ const selected = activeConnections[next];
8082
+ if (selected) {
8083
+ setNotice(
8084
+ `Connection selected: ${truncateForTerminal(recordLabel(selected, "connection"), 80)}`
8085
+ );
8086
+ } else {
8087
+ setNotice("No connection rows are available to select.");
8088
+ }
8089
+ return next;
8090
+ });
8091
+ },
8092
+ [activeConnections]
8093
+ );
8094
+ useEffect4(() => {
8095
+ setSelectedConnectionIndex(
8096
+ (current) => activeConnections.length ? Math.min(Math.max(0, current), activeConnections.length - 1) : 0
8097
+ );
8098
+ }, [activeConnections.length]);
8099
+ useEffect4(() => {
8100
+ setSlashCommandCompletionIndex(0);
8101
+ }, [input]);
8102
+ useEffect4(() => {
8103
+ setSlashCommandCompletionIndex(
8104
+ (current) => slashCommandCompletions.length ? Math.min(current, slashCommandCompletions.length - 1) : 0
8105
+ );
8106
+ }, [slashCommandCompletions.length]);
8107
+ const reportsPanelState = workspacePanelStore.reports ?? createWorkspacePanelState("reports", "idle");
8108
+ const reportsDashboardModel = useMemo(
8109
+ () => reportsPanelState.status === "ready" ? buildReportsDashboardModel({
8110
+ dashboard: reportsPanelState.data.dashboard,
8111
+ reportsSummary: reportsPanelState.data.reportsSummary,
8112
+ selectedProject,
8113
+ costReport: reportsPanelState.data.costReport,
8114
+ wafReport: reportsPanelState.data.wafReport
8115
+ }) : void 0,
8116
+ [reportsPanelState.data, reportsPanelState.status, selectedProject]
8117
+ );
8118
+ const chatArtifactChips = useMemo(
8119
+ () => buildChatArtifactChips({
8120
+ projectName: selectedProject?.name ?? defaultProject.name,
8121
+ reportsStatus: reportsPanelState.status,
8122
+ coverageLabel: reportsDashboardModel?.coverageLabel,
8123
+ topActionCount: reportsDashboardModel?.topActions.length ?? 0,
8124
+ frontendThreadUrl
8125
+ }),
8126
+ [
8127
+ frontendThreadUrl,
8128
+ reportsDashboardModel?.coverageLabel,
8129
+ reportsDashboardModel?.topActions.length,
8130
+ reportsPanelState.status,
8131
+ selectedProject?.name
8132
+ ]
8133
+ );
6905
8134
  const activeTablePage = tablePageByTab[activeWorkspaceTab] ?? 0;
6906
8135
  const bannerRenderedRows = bannerDisabled ? 0 : estimateBannerRows({ detailsCount: headerDetails.length, columns: bannerContentColumns });
6907
8136
  const workspaceContentWidth = Math.max(24, terminalSize.columns - tuiLayout.paddingX * 2 - 3);
@@ -6943,7 +8172,8 @@ var App = ({
6943
8172
  suggestionRows: promptSuggestionRows,
6944
8173
  compact: promptControlsCompact,
6945
8174
  hasThinkingSteps,
6946
- includeControls: !splitChatLayout
8175
+ includeControls: !splitChatLayout && promptFooterControlsVisible,
8176
+ variant: "dock"
6947
8177
  });
6948
8178
  const searchPromptPanelRows = estimatePromptPanelRows({
6949
8179
  inputRows: promptInputRows,
@@ -6971,6 +8201,7 @@ var App = ({
6971
8201
  footerRows: chatFooterRows
6972
8202
  });
6973
8203
  const chatMiddleAuxiliaryRows = (isSearching ? 3 : 0) + (queuedMessages.length > 0 ? 4 : 0) + (notice ? 1 : 0) + (errorText ? 5 : 0) + (chatState.status === "hitl_waiting" && chatState.hitl?.waiting ? 7 : 0) + (activeSelector ? tuiLayout.selectorLimit + 4 : 0);
8204
+ const contextRailArtifactRows = splitChatLayout && chatArtifactChips.length ? chatArtifactChips.length + 2 : 0;
6974
8205
  const chatMainPanelRows = Math.max(4, chatMiddleViewportRows - chatMiddleAuxiliaryRows);
6975
8206
  const chatThreadHeight = getFramedBodyRows(chatMainPanelRows);
6976
8207
  const chatMiddleOverflowing = chatMiddleContentHeight > chatMiddleViewportHeight;
@@ -6980,9 +8211,10 @@ var App = ({
6980
8211
  return Math.max(1, terminalSize.rows - (promptControlsCompact ? 7 : 5));
6981
8212
  }
6982
8213
  const auxiliaryRows = (isSearching ? 3 : 0) + (queuedMessages.length > 0 ? 4 : 0) + (notice ? 1 : 0) + (errorText ? 5 : 0);
6983
- return workspaceTabStartRow + 9 + auxiliaryRows;
8214
+ return workspaceTabStartRow + 9 + auxiliaryRows + contextRailArtifactRows;
6984
8215
  },
6985
8216
  [
8217
+ contextRailArtifactRows,
6986
8218
  errorText,
6987
8219
  isSearching,
6988
8220
  notice,
@@ -7019,7 +8251,8 @@ var App = ({
7019
8251
  suggestionRows: promptSuggestionRows,
7020
8252
  compact: promptControlsCompact,
7021
8253
  hasThinkingSteps,
7022
- includeControls: !splitChatLayout
8254
+ includeControls: !splitChatLayout && promptFooterControlsVisible,
8255
+ variant: "dock"
7023
8256
  }) + 1
7024
8257
  ),
7025
8258
  [
@@ -7028,6 +8261,7 @@ var App = ({
7028
8261
  promptSuggestionRows,
7029
8262
  terminalSize.rows,
7030
8263
  promptControlsCompact,
8264
+ promptFooterControlsVisible,
7031
8265
  splitChatLayout
7032
8266
  ]
7033
8267
  );
@@ -7069,6 +8303,20 @@ var App = ({
7069
8303
  }
7070
8304
  void sendMessage(question);
7071
8305
  };
8306
+ const chooseSlashCommandCompletion = (command) => {
8307
+ completionCycleRef.current = void 0;
8308
+ setSlashCommandCompletionIndex(0);
8309
+ handlePromptSubmit(command.name);
8310
+ };
8311
+ const moveSlashCommandCompletion = (direction) => {
8312
+ if (!slashCommandCompletions.length) {
8313
+ return;
8314
+ }
8315
+ completionCycleRef.current = void 0;
8316
+ setSlashCommandCompletionIndex(
8317
+ (current) => (current + direction + slashCommandCompletions.length) % slashCommandCompletions.length
8318
+ );
8319
+ };
7072
8320
  const handleFocusedControlEnter = () => {
7073
8321
  if (isSelectorControlFocus(focusedControl)) {
7074
8322
  setActiveSelector(focusedControl);
@@ -7216,6 +8464,29 @@ var App = ({
7216
8464
  exit();
7217
8465
  return;
7218
8466
  }
8467
+ const selectionDirection = key.downArrow || lowerInput === "j" ? 1 : key.upArrow || lowerInput === "k" ? -1 : void 0;
8468
+ if (selectionDirection && selectableWorkspaceTab(activeWorkspaceTab) && !key.ctrl && !key.meta) {
8469
+ if (activeWorkspaceTab === "projects") {
8470
+ moveSelectedProject(selectionDirection);
8471
+ } else {
8472
+ moveSelectedConnection(selectionDirection);
8473
+ }
8474
+ return;
8475
+ }
8476
+ if (key.return && selectableWorkspaceTab(activeWorkspaceTab)) {
8477
+ if (activeWorkspaceTab === "projects") {
8478
+ const project = activeProjectList[selectedProjectIndex];
8479
+ setNotice(
8480
+ project ? `Project selected for chat context: ${truncateForTerminal(project.name, 80)}` : "No project rows are available to select."
8481
+ );
8482
+ } else {
8483
+ const connection = activeConnections[selectedConnectionIndex];
8484
+ setNotice(
8485
+ connection ? `Connection selected: ${truncateForTerminal(recordLabel(connection, "connection"), 80)}` : "No connection rows are available to select."
8486
+ );
8487
+ }
8488
+ return;
8489
+ }
7219
8490
  if (key.upArrow && !key.ctrl && !key.meta) {
7220
8491
  workspaceScrollViewRef.current?.scrollBy(-1);
7221
8492
  return;
@@ -7325,7 +8596,15 @@ var App = ({
7325
8596
  }
7326
8597
  return;
7327
8598
  }
7328
- if (promptInputActive && (key.tab || inputKey === " ") && input.trimStart().startsWith("/")) {
8599
+ if (slashCommandCompletionsActive && (key.tab || inputKey === " " || key.downArrow) && !key.ctrl && !key.meta) {
8600
+ moveSlashCommandCompletion(1);
8601
+ return;
8602
+ }
8603
+ if (slashCommandCompletionsActive && key.upArrow && !key.ctrl && !key.meta) {
8604
+ moveSlashCommandCompletion(-1);
8605
+ return;
8606
+ }
8607
+ if (promptInputActive && (key.tab || inputKey === " ") && input.trimStart().startsWith("/") && !slashCommandCompletionsActive) {
7329
8608
  const completion = completePromptInput(
7330
8609
  input,
7331
8610
  promptCompletionContext,
@@ -7399,6 +8678,20 @@ var App = ({
7399
8678
  return;
7400
8679
  }
7401
8680
  }
8681
+ if (phase === "ready" && activeWorkspaceTab === "chat" && !promptInputActive && !activeSelector && !isSearching) {
8682
+ if (lowerInput === "y") {
8683
+ copyLatestAssistantResponse();
8684
+ return;
8685
+ }
8686
+ if (lowerInput === "d") {
8687
+ downloadChatTranscript();
8688
+ return;
8689
+ }
8690
+ if (lowerInput === "o") {
8691
+ handleOpenFrontend();
8692
+ return;
8693
+ }
8694
+ }
7402
8695
  if (phase === "ready" && (!input.trim() || !promptInputActive)) {
7403
8696
  if (key.tab || inputKey === " ") {
7404
8697
  setFocusedControl((current) => nextControlFocus(current, controlFocusOrder));
@@ -7527,6 +8820,7 @@ var App = ({
7527
8820
  stopActiveChat();
7528
8821
  }
7529
8822
  if (key.ctrl && inputKey.toLowerCase() === "l") {
8823
+ setActiveDraftSessionKey(newDraftChatSessionKey());
7530
8824
  setChatState((prev) => ({
7531
8825
  ...initialChatState,
7532
8826
  status: "idle",
@@ -7740,6 +9034,7 @@ var App = ({
7740
9034
  state: activeWorkspacePanelState,
7741
9035
  projects,
7742
9036
  selectedProject,
9037
+ selectedConnectionIndex,
7743
9038
  currentUserId,
7744
9039
  selectedModel,
7745
9040
  selectedMode,
@@ -7856,14 +9151,23 @@ var App = ({
7856
9151
  ]
7857
9152
  }
7858
9153
  ) : null,
9154
+ !splitChatLayout && chatArtifactChips.length ? /* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(
9155
+ ArtifactStrip,
9156
+ {
9157
+ chips: chatArtifactChips,
9158
+ terminalColumns: chatAvailableWidth
9159
+ }
9160
+ ) }) : null,
7859
9161
  splitChatLayout ? /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", columnGap: chatSplitGap, children: [
7860
9162
  /* @__PURE__ */ jsx10(
7861
9163
  ChatContextPanel,
7862
9164
  {
7863
9165
  width: chatContextPanelWidth,
7864
9166
  height: chatMainPanelRows,
9167
+ mode: chatResponsiveMode,
7865
9168
  active: activeChatPanel === "settings",
7866
9169
  focused: focusedControl,
9170
+ artifactChips: chatArtifactChips,
7867
9171
  selectedThreadTitle,
7868
9172
  selectedProject,
7869
9173
  selectedModel,
@@ -7904,7 +9208,7 @@ var App = ({
7904
9208
  selectedIndex: Math.max(
7905
9209
  0,
7906
9210
  threadSelectItems.findIndex(
7907
- (item) => item.value.kind === "remote" && item.value.thread.thread_id === chatState.threadId || item.value.kind === "session" && item.value.session.threadId === chatState.threadId
9211
+ (item) => item.value.kind === "remote" && item.value.thread.thread_id === chatState.threadId || item.value.kind === "session" && item.value.session.threadId === chatState.threadId || item.value.kind === "draft" && item.value.draft.key === activeDraftSessionKey
7908
9212
  )
7909
9213
  ),
7910
9214
  onSubmit: (item) => selectThread(item.value),
@@ -8023,6 +9327,7 @@ var App = ({
8023
9327
  ) : activeSelector ? null : /* @__PURE__ */ jsx10(
8024
9328
  InputBox,
8025
9329
  {
9330
+ variant: "dock",
8026
9331
  value: input,
8027
9332
  onChange: handlePromptChange,
8028
9333
  onSubmit: handlePromptSubmit,
@@ -8044,6 +9349,10 @@ var App = ({
8044
9349
  followUpsLabel: promptSuggestions.label,
8045
9350
  focusedFollowUpIndex,
8046
9351
  followUpsActive: focusedFollowUpIndex !== void 0,
9352
+ commandCompletions: slashCommandCompletions,
9353
+ focusedCommandCompletionIndex: activeSlashCommandCompletionIndex,
9354
+ commandCompletionsActive: slashCommandCompletionsActive,
9355
+ onCommandCompletionSubmit: chooseSlashCommandCompletion,
8047
9356
  terminalColumns: terminalSize.columns,
8048
9357
  scrollOffset: promptInputViewport.startRow,
8049
9358
  minInputRows: promptInputRowBudget,
@@ -8055,7 +9364,7 @@ var App = ({
8055
9364
  selectorOpen: Boolean(activeSelector),
8056
9365
  searching: isSearching
8057
9366
  }),
8058
- footerControls: promptFooterControls,
9367
+ footerControls: slashCommandCompletionsActive ? void 0 : promptFooterControls,
8059
9368
  helpText: "",
8060
9369
  actionLabel: promptActionIsCancel ? "ESC to cancel" : void 0,
8061
9370
  actionHint: getChatInputHelpText({
@@ -8077,5 +9386,6 @@ var App = ({
8077
9386
  ] });
8078
9387
  };
8079
9388
  export {
8080
- App
9389
+ App,
9390
+ buildTuiHeaderDetails
8081
9391
  };