@kal-elsam/kairo-runtime 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +14 -8
  2. package/package.json +1 -1
  3. package/scripts/cockpit-smoke.mjs +3 -3
  4. package/scripts/ux-prototype-tty.mjs +9 -0
  5. package/src/global/control-plane-proposals.js +2 -1
  6. package/src/global/control-plane-snapshot.js +1 -1
  7. package/src/global/ink/brand/wordmark.js +50 -0
  8. package/src/global/ink/cockpit/primitives.js +72 -50
  9. package/src/global/ink/cockpit-changes.js +2 -2
  10. package/src/global/ink/cockpit-control-center.js +12 -64
  11. package/src/global/ink/cockpit-controller.js +41 -5
  12. package/src/global/ink/cockpit-enter.js +1 -0
  13. package/src/global/ink/cockpit-models.js +14 -7
  14. package/src/global/ink/cockpit-palette.js +18 -7
  15. package/src/global/ink/cockpit-recovery.js +9 -6
  16. package/src/global/ink/cockpit-usage.js +111 -0
  17. package/src/global/ink/cockpit-views.js +70 -97
  18. package/src/global/ink/orchestrator-app.js +44 -4
  19. package/src/global/ink/setup-app.js +55 -72
  20. package/src/global/ink/setup-state.js +16 -0
  21. package/src/global/ink/theme.js +40 -8
  22. package/src/global/ink/ux/live-activity.js +191 -0
  23. package/src/global/ink/ux/live-alerts.js +156 -0
  24. package/src/global/ink/ux/live-governance.js +191 -0
  25. package/src/global/ink/ux/live-orchestration.js +185 -0
  26. package/src/global/ink/ux/live-overview.js +175 -0
  27. package/src/global/ink/ux/live-settings.js +186 -0
  28. package/src/global/ink/ux/live-setup.js +160 -0
  29. package/src/global/ink/ux/live-usage.js +49 -0
  30. package/src/global/ink/ux/semantic.js +101 -0
  31. package/src/global/ink/ux/task-flow-app.js +85 -0
  32. package/src/global/ink/ux/task-flow.js +173 -0
  33. package/src/global/orchestrator.js +37 -20
@@ -0,0 +1,191 @@
1
+ /** Live semantic Governance. Ownership: Callout=status · Confirm/primary=action · footer=keys. */
2
+ import React from "react";
3
+ import { Box, Text } from "ink";
4
+ import { formatConfirmPath } from "../cockpit-path-label.js";
5
+ import { CHANGES_PHASE } from "../cockpit-changes.js";
6
+ import { LAYOUT_MODES } from "../layout.js";
7
+ import { ActionList, Callout, Confirm, Details, ViewTitle } from "./semantic.js";
8
+ import { mapHealthTone } from "./live-overview.js";
9
+
10
+ function healthLabel(kind) {
11
+ return String(kind ?? "unknown").replaceAll("_", " ");
12
+ }
13
+
14
+ function plannedChanges(snapshot, changesAction) {
15
+ const preview = changesAction?.preview;
16
+ const diff = snapshot?.diff;
17
+ if (preview?.hasChanges) return preview.changes ?? [];
18
+ if (diff?.hasChanges && !preview) return diff.changes ?? [];
19
+ return [];
20
+ }
21
+
22
+ function phaseTone(phase, healthKind) {
23
+ if (phase === CHANGES_PHASE.FAILED) return "danger";
24
+ if (phase === CHANGES_PHASE.COMPLETED) return "ready";
25
+ if (phase === CHANGES_PHASE.CONFIRMING || phase === CHANGES_PHASE.PREVIEWING || phase === CHANGES_PHASE.APPLYING) {
26
+ return "warn";
27
+ }
28
+ return mapHealthTone(healthKind);
29
+ }
30
+
31
+ function phaseTitle(phase, snapshot, changesAction) {
32
+ if (phase === CHANGES_PHASE.PREVIEWING) return "Previewing";
33
+ if (phase === CHANGES_PHASE.CONFIRMING) return "Confirm apply";
34
+ if (phase === CHANGES_PHASE.APPLYING) return "Applying";
35
+ if (phase === CHANGES_PHASE.COMPLETED) return "Apply complete";
36
+ if (phase === CHANGES_PHASE.FAILED) {
37
+ return changesAction?.error === "setup-required" ? "Setup required" : "Governance failed";
38
+ }
39
+ const pending = snapshot?.diff?.hasChanges
40
+ ? (snapshot.diff.changeCount ?? snapshot.diff.changes?.length ?? 0)
41
+ : 0;
42
+ return `${healthLabel(snapshot?.health)}${pending > 0 ? ` · ${pending} pending` : " · drift clean"}`;
43
+ }
44
+
45
+ /** Compact/minimal: 3 paths; wide: 12. */
46
+ export function detailsPathLimit(layoutMode = LAYOUT_MODES.COMPACT) {
47
+ return layoutMode === LAYOUT_MODES.WIDE ? 12 : 3;
48
+ }
49
+
50
+ function calloutBody(phase, changesAction) {
51
+ if (phase !== CHANGES_PHASE.FAILED) return "";
52
+ if (changesAction?.error === "setup-required") return "Not configured — open Overview and run setup.";
53
+ if (changesAction?.error) return `Error · ${changesAction.error}`;
54
+ return changesAction?.message ?? "";
55
+ }
56
+
57
+ function buildDetailsLines(planned, showPaths, changesAction, homeDir, pathLimit) {
58
+ if (!showPaths) return [];
59
+ if (planned.length > 0) {
60
+ const lines = planned.slice(0, pathLimit).map((c) =>
61
+ `${c.action ?? c.kind} · ${formatConfirmPath(c.target, homeDir)}`
62
+ );
63
+ if (planned.length > pathLimit) lines.push(`… ${planned.length - pathLimit} more`);
64
+ return lines;
65
+ }
66
+ const receipt = changesAction?.receipt;
67
+ if (receipt?.checksBefore && receipt?.checksAfter) {
68
+ return [`Checks · before ok=${receipt.checksBefore.ok} → after ok=${receipt.checksAfter.ok}`];
69
+ }
70
+ return ["No path evidence on this scan."];
71
+ }
72
+
73
+ /** Pure adapter: paths only in detailsLines (confirming or Details open). */
74
+ export function adaptGovernanceModel({
75
+ snapshot = null,
76
+ changesAction = null,
77
+ homeDir = null,
78
+ detailsOpen = false,
79
+ layoutMode = LAYOUT_MODES.COMPACT
80
+ } = {}) {
81
+ const phase = changesAction?.phase ?? CHANGES_PHASE.IDLE;
82
+ const coverage = snapshot?.coverage ?? {};
83
+ const cta = snapshot?.cta;
84
+ const planned = plannedChanges(snapshot, changesAction);
85
+ const showPaths = detailsOpen || phase === CHANGES_PHASE.CONFIRMING;
86
+ const confirming = phase === CHANGES_PHASE.CONFIRMING;
87
+ const working = phase === CHANGES_PHASE.PREVIEWING || phase === CHANGES_PHASE.APPLYING;
88
+ const actionable = phase === CHANGES_PHASE.IDLE
89
+ || phase === CHANGES_PHASE.COMPLETED
90
+ || phase === CHANGES_PHASE.FAILED;
91
+
92
+ const metrics = [
93
+ {
94
+ id: "coverage",
95
+ label: `Coverage · ${coverage.governedAgents ?? 0}/${coverage.detectedAgents ?? 0} agents · ${coverage.components ?? 0} components`
96
+ },
97
+ {
98
+ id: "planned",
99
+ label: planned.length > 0
100
+ ? `Planned · ${planned.length} change(s)`
101
+ : (snapshot?.diff?.summary ?? "No pending governance changes.")
102
+ }
103
+ ];
104
+ if (changesAction?.receipt) {
105
+ const r = changesAction.receipt;
106
+ metrics.push({
107
+ id: "receipt",
108
+ label: `Result · ${r.action}${r.partial ? " (partial)" : ""}${r.backups?.length ? ` · ${r.backups.length} backup(s)` : ""}`
109
+ });
110
+ }
111
+
112
+ return {
113
+ title: "Governance",
114
+ phase,
115
+ callout: {
116
+ tone: phaseTone(phase, snapshot?.health),
117
+ title: phaseTitle(phase, snapshot, changesAction),
118
+ body: calloutBody(phase, changesAction)
119
+ },
120
+ primary: confirming || working
121
+ ? null
122
+ : {
123
+ label: cta?.title ?? "Review governance when ready",
124
+ detail: actionable ? (cta?.detail ?? null) : null
125
+ },
126
+ confirm: confirming
127
+ ? {
128
+ summary: planned.length > 0
129
+ ? `Apply ${planned.length} planned change(s).`
130
+ : "Apply confirmed governance preview.",
131
+ primaryLabel: "Apply"
132
+ }
133
+ : null,
134
+ metrics,
135
+ details: buildDetailsLines(planned, showPaths, changesAction, homeDir, detailsPathLimit(layoutMode)),
136
+ detailsOpen: showPaths
137
+ };
138
+ }
139
+
140
+ export function SemanticGovernancePanel({
141
+ snapshot = null,
142
+ changesAction = null,
143
+ homeDir = null,
144
+ detailsOpen = false,
145
+ layoutMode = LAYOUT_MODES.COMPACT,
146
+ colorEnabled = true,
147
+ unicode = true
148
+ }) {
149
+ const view = adaptGovernanceModel({
150
+ snapshot, changesAction, homeDir, detailsOpen, layoutMode
151
+ });
152
+ return React.createElement(Box, { flexDirection: "column" },
153
+ React.createElement(ViewTitle, { colorEnabled }, view.title),
154
+ React.createElement(Callout, {
155
+ tone: view.callout.tone,
156
+ title: view.callout.title,
157
+ body: view.callout.body || undefined,
158
+ colorEnabled,
159
+ compact: true
160
+ }),
161
+ view.confirm
162
+ ? React.createElement(Confirm, {
163
+ summary: view.confirm.summary,
164
+ primaryLabel: view.confirm.primaryLabel,
165
+ focused: false,
166
+ colorEnabled,
167
+ mark: " "
168
+ })
169
+ : view.primary
170
+ ? React.createElement(Box, { flexDirection: "column" },
171
+ React.createElement(Text, { bold: true }, ` ${view.primary.label}`),
172
+ view.primary.detail ? React.createElement(Text, null, view.primary.detail) : null
173
+ )
174
+ : null,
175
+ React.createElement(ActionList, {
176
+ items: view.metrics,
177
+ selectedIndex: -1,
178
+ focused: false,
179
+ colorEnabled,
180
+ unicode
181
+ }),
182
+ React.createElement(Details, {
183
+ open: view.detailsOpen,
184
+ summary: "Details",
185
+ lines: view.details.length > 0 ? view.details : ["No path evidence on this scan."],
186
+ colorEnabled,
187
+ focused: false,
188
+ mark: " "
189
+ })
190
+ );
191
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Live semantic Orchestration lists (Hub / Active / History / Reviews).
3
+ * ActionList owns the only focus mark; windowSlice keeps full domain navigable.
4
+ * Enter destinations unchanged — adapter exposes focused identity for listIndex.
5
+ */
6
+ import React from "react";
7
+ import { Box, Text } from "ink";
8
+ import { LAYOUT_MODES } from "../layout.js";
9
+ import { ORCHESTRATOR_VIEWS, formatRunLines } from "../orchestrator-state.js";
10
+ import {
11
+ RUNS_HUB_ITEMS, formatOrchestrationStatus, formatRunsHubLines
12
+ } from "../cockpit-runs.js";
13
+ import { formatReviewListLines } from "../cockpit-reviews.js";
14
+ import { ActionList, Callout, ViewTitle } from "./semantic.js";
15
+ import { windowSlice } from "./live-activity.js";
16
+
17
+ export function orchestrationListLimit(layoutMode = LAYOUT_MODES.COMPACT) {
18
+ return layoutMode === LAYOUT_MODES.WIDE ? 8 : 3;
19
+ }
20
+
21
+ function emptyPack(label) {
22
+ return {
23
+ items: [{ id: "empty", label }],
24
+ selectedIndex: -1,
25
+ focusedId: null,
26
+ total: 0,
27
+ start: 0,
28
+ isEmpty: true
29
+ };
30
+ }
31
+
32
+ function windowDomain(domain, listIndex, limit, toItem, emptyLabel) {
33
+ const windowed = windowSlice(domain, listIndex, limit);
34
+ if (windowed.items.length === 0) return emptyPack(emptyLabel);
35
+ const safe = Math.min(Math.max(0, listIndex), domain.length - 1);
36
+ return {
37
+ items: windowed.items.map((entry, i) => toItem(entry, windowed.start + i)),
38
+ selectedIndex: windowed.selectedIndex,
39
+ focusedId: toItem(domain[safe], safe).id,
40
+ total: domain.length,
41
+ start: windowed.start,
42
+ isEmpty: false
43
+ };
44
+ }
45
+
46
+ function hubPack(listIndex) {
47
+ const labels = formatRunsHubLines(RUNS_HUB_ITEMS);
48
+ const items = RUNS_HUB_ITEMS.map((item, i) => ({
49
+ id: item.id,
50
+ label: labels[i] ?? item.label
51
+ }));
52
+ const safe = Math.min(Math.max(0, listIndex), items.length - 1);
53
+ return {
54
+ items,
55
+ selectedIndex: safe,
56
+ focusedId: items[safe]?.id ?? null,
57
+ total: items.length,
58
+ start: 0,
59
+ isEmpty: false
60
+ };
61
+ }
62
+
63
+ function runPack(runs, listIndex, limit, emptyLabel) {
64
+ return windowDomain(runs, listIndex, limit, (run, absoluteIndex) => ({
65
+ id: run.runId ?? `run-${absoluteIndex}`,
66
+ label: formatRunLines([run], { readable: true })[0]
67
+ }), emptyLabel);
68
+ }
69
+
70
+ function reviewPack(reviews, listIndex, limit) {
71
+ const empty = "No review receipts yet. Run kairo review --agent codex|pi.";
72
+ return windowDomain(reviews, listIndex, limit, (receipt, absoluteIndex) => ({
73
+ id: receipt.reviewId ?? `review-${absoluteIndex}`,
74
+ label: formatReviewListLines([receipt])[0]
75
+ }), empty);
76
+ }
77
+
78
+ /** Pure adapter: Hub · Active · History · Reviews. */
79
+ export function adaptOrchestrationModel({
80
+ view = ORCHESTRATOR_VIEWS.RUNS,
81
+ dashboard = null,
82
+ reviews = [],
83
+ listIndex = 0,
84
+ layoutMode = LAYOUT_MODES.COMPACT
85
+ } = {}) {
86
+ const limit = orchestrationListLimit(layoutMode);
87
+ const active = dashboard?.activeRuns ?? [];
88
+ const recent = dashboard?.recentRuns ?? [];
89
+ const reviewList = Array.isArray(reviews) ? reviews : [];
90
+ const status = formatOrchestrationStatus({
91
+ active: active.length, recent: recent.length, reviews: reviewList.length
92
+ });
93
+
94
+ let title = "Orchestration";
95
+ let callout = { tone: active.length > 0 ? "warn" : "info", title: status, body: "" };
96
+ let list = hubPack(listIndex);
97
+
98
+ if (view === ORCHESTRATOR_VIEWS.RUNS) {
99
+ callout = {
100
+ tone: active.length > 0 ? "warn" : "info",
101
+ title: status,
102
+ body: "Choose Active runs, History, Reviews, or New run."
103
+ };
104
+ list = hubPack(listIndex);
105
+ } else if (view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS) {
106
+ title = "Active runs";
107
+ list = runPack(
108
+ active, listIndex, limit,
109
+ "No runs executing. Governance first — launch only after setup/repairs."
110
+ );
111
+ callout = {
112
+ tone: list.isEmpty ? "info" : "warn",
113
+ title: list.isEmpty ? "None active" : `${list.total} active`,
114
+ body: list.isEmpty
115
+ ? "Esc back to Orchestration"
116
+ : "Enter opens detail"
117
+ };
118
+ } else if (view === ORCHESTRATOR_VIEWS.RECENT_RUNS) {
119
+ title = "Run history";
120
+ list = runPack(recent, listIndex, limit, "No completed runs yet.");
121
+ callout = {
122
+ tone: "info",
123
+ title: list.isEmpty ? "None completed" : `${list.total} completed`,
124
+ body: list.isEmpty
125
+ ? "Esc back to Orchestration"
126
+ : "Enter opens detail"
127
+ };
128
+ } else if (view === ORCHESTRATOR_VIEWS.REVIEWS) {
129
+ title = "Reviews";
130
+ list = reviewPack(reviewList, listIndex, limit);
131
+ callout = {
132
+ tone: "info",
133
+ title: list.isEmpty ? "None yet" : `${list.total} receipts`,
134
+ body: list.isEmpty
135
+ ? "Launch via kairo review --agent codex|pi."
136
+ : "Read-only · Enter opens detail"
137
+ };
138
+ }
139
+
140
+ return {
141
+ title,
142
+ view,
143
+ callout,
144
+ items: list.items,
145
+ selectedIndex: list.selectedIndex,
146
+ focusedId: list.focusedId,
147
+ total: list.total,
148
+ start: list.start,
149
+ isEmpty: list.isEmpty,
150
+ listLimit: limit
151
+ };
152
+ }
153
+
154
+ export function SemanticOrchestrationPanel({
155
+ view = ORCHESTRATOR_VIEWS.RUNS,
156
+ dashboard = null,
157
+ reviews = [],
158
+ listIndex = 0,
159
+ layoutMode = LAYOUT_MODES.COMPACT,
160
+ contentFocused = false,
161
+ colorEnabled = true,
162
+ unicode = true
163
+ }) {
164
+ const model = adaptOrchestrationModel({
165
+ view, dashboard, reviews, listIndex, layoutMode
166
+ });
167
+ const listFocused = contentFocused && !model.isEmpty;
168
+ return React.createElement(Box, { flexDirection: "column" },
169
+ React.createElement(ViewTitle, { colorEnabled }, model.title),
170
+ React.createElement(Callout, {
171
+ tone: model.callout.tone,
172
+ title: model.callout.title,
173
+ body: model.callout.body || undefined,
174
+ colorEnabled,
175
+ compact: true
176
+ }),
177
+ React.createElement(ActionList, {
178
+ items: model.items,
179
+ selectedIndex: model.selectedIndex,
180
+ focused: listFocused,
181
+ colorEnabled,
182
+ unicode
183
+ })
184
+ );
185
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Live semantic Overview for Cockpit HOME — product cover.
3
+ * Nav owns the sole focus mark — this panel never renders `>`.
4
+ * ASCII wordmark only here (wide/compact); minimal is textual.
5
+ */
6
+ import React from "react";
7
+ import { Box, Text } from "ink";
8
+ import { CONTROL_PLANE_HEALTH } from "../../control-plane-snapshot.js";
9
+ import { LAYOUT_MODES } from "../layout.js";
10
+ import { COCKPIT_COLORS } from "../theme.js";
11
+ import {
12
+ overviewBrandTitle,
13
+ shouldShowWordmark,
14
+ wordmarkLines
15
+ } from "../brand/wordmark.js";
16
+ import { ActionList, Callout, Details } from "./semantic.js";
17
+
18
+ const DESTINATION_LABELS = {
19
+ setup: "Setup",
20
+ changes: "Governance",
21
+ ides: "Agents",
22
+ runs: "Orchestration",
23
+ "control-center": "Overview",
24
+ activity: "Activity",
25
+ usage: "Usage",
26
+ profile: "Settings"
27
+ };
28
+
29
+ export function mapHealthTone(kind) {
30
+ switch (kind) {
31
+ case CONTROL_PLANE_HEALTH.CHECK_FAILED:
32
+ return "danger";
33
+ case CONTROL_PLANE_HEALTH.ACTION_REQUIRED:
34
+ case CONTROL_PLANE_HEALTH.NOT_CONFIGURED:
35
+ case CONTROL_PLANE_HEALTH.HEALTHY_WITH_NOTES:
36
+ return "warn";
37
+ case CONTROL_PLANE_HEALTH.HEALTHY:
38
+ return "ready";
39
+ default:
40
+ return "warn";
41
+ }
42
+ }
43
+
44
+ function humanizeDestination(destination) {
45
+ if (!destination) return null;
46
+ return DESTINATION_LABELS[destination] ?? null;
47
+ }
48
+
49
+ /** Safe Details lines only — never invent paths/IDs; honest empty when none. */
50
+ export function buildOverviewDetails(model = {}) {
51
+ const lines = [];
52
+ const next = model.nextAction ?? model.cta ?? {};
53
+ const dest = humanizeDestination(next.destination);
54
+ if (dest) lines.push(`Next destination · ${dest}`);
55
+ if (typeof model.alerts?.count === "number") {
56
+ lines.push(`Open alerts · ${model.alerts.count}`);
57
+ }
58
+ if (lines.length === 0) return ["No extra evidence beyond the metrics above."];
59
+ return lines;
60
+ }
61
+
62
+ /**
63
+ * Pure adapter: buildControlCenterModel → semantic overview props.
64
+ * Callout / CTA / metrics never include paths or IDs.
65
+ */
66
+ export function adaptControlCenterToOverview(model = {}) {
67
+ const status = model.status ?? model.health ?? {};
68
+ const next = model.nextAction ?? model.cta ?? {};
69
+ return {
70
+ title: model.title ?? "Overview",
71
+ callout: {
72
+ tone: mapHealthTone(status.kind),
73
+ title: status.label ?? "Unknown",
74
+ body: status.summaryLine ?? ""
75
+ },
76
+ primary: {
77
+ label: next.actionTitle ?? "Review control plane",
78
+ detail: next.actionDetail || null,
79
+ hint: next.enterHint ?? null
80
+ },
81
+ metrics: [
82
+ { id: "activity", label: `Activity · ${model.activity?.headline ?? "Idle"}` },
83
+ { id: "alerts", label: `Alerts · ${model.alerts?.headline ?? "Alert data unavailable"}` },
84
+ { id: "tokens", label: `Tokens · ${model.tokens?.headline ?? "Data unavailable"}` }
85
+ ],
86
+ details: buildOverviewDetails(model)
87
+ };
88
+ }
89
+
90
+ function renderWordmark({ layoutMode, colorEnabled = true, unicode = true }) {
91
+ const lines = wordmarkLines(layoutMode, { unicode });
92
+ if (lines.length === 0) return null;
93
+ return React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
94
+ ...lines.map((line, i) => React.createElement(Text, {
95
+ key: `wm-${i}`,
96
+ bold: i === 0,
97
+ color: colorEnabled ? COCKPIT_COLORS.brand : undefined
98
+ }, line))
99
+ );
100
+ }
101
+
102
+ function renderCallout(view, colorEnabled) {
103
+ return React.createElement(Callout, {
104
+ tone: view.callout.tone,
105
+ title: view.callout.title,
106
+ body: view.callout.body,
107
+ colorEnabled,
108
+ compact: true
109
+ });
110
+ }
111
+
112
+ export function SemanticOverviewPanel({
113
+ model,
114
+ detailsOpen = false,
115
+ colorEnabled = true,
116
+ unicode = true,
117
+ layoutMode = LAYOUT_MODES.COMPACT
118
+ }) {
119
+ const view = adaptControlCenterToOverview(model);
120
+ const showArt = shouldShowWordmark(layoutMode);
121
+ const brandTitle = overviewBrandTitle(layoutMode);
122
+ const isWide = layoutMode === LAYOUT_MODES.WIDE;
123
+ const mark = renderWordmark({ layoutMode, colorEnabled, unicode });
124
+ const status = renderCallout(view, colorEnabled);
125
+
126
+ const hero = showArt
127
+ ? (isWide
128
+ ? React.createElement(Box, { flexDirection: "row", marginBottom: 1 },
129
+ React.createElement(Box, { marginRight: 2 }, mark),
130
+ React.createElement(Box, { flexDirection: "column", flexGrow: 1 }, status)
131
+ )
132
+ : React.createElement(Box, { flexDirection: "column" }, mark, status))
133
+ : React.createElement(Box, { flexDirection: "column" },
134
+ React.createElement(Text, {
135
+ bold: true,
136
+ color: colorEnabled ? COCKPIT_COLORS.brand : undefined
137
+ }, brandTitle),
138
+ status
139
+ );
140
+
141
+ return React.createElement(Box, { flexDirection: "column" },
142
+ hero,
143
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
144
+ React.createElement(Text, {
145
+ bold: true,
146
+ color: colorEnabled ? COCKPIT_COLORS.interactive : undefined
147
+ }, view.primary.label),
148
+ view.primary.detail
149
+ ? React.createElement(Text, null, view.primary.detail)
150
+ : null,
151
+ view.primary.hint
152
+ ? React.createElement(Text, {
153
+ color: colorEnabled ? COCKPIT_COLORS.muted : undefined
154
+ }, view.primary.hint)
155
+ : null
156
+ ),
157
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
158
+ React.createElement(ActionList, {
159
+ items: view.metrics,
160
+ selectedIndex: -1,
161
+ focused: false,
162
+ colorEnabled,
163
+ unicode
164
+ })
165
+ ),
166
+ React.createElement(Details, {
167
+ open: detailsOpen,
168
+ summary: "Details",
169
+ lines: view.details,
170
+ colorEnabled,
171
+ focused: false,
172
+ mark: " "
173
+ })
174
+ );
175
+ }