triagent 0.1.0-alpha9 → 0.1.0-beta2

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 (47) hide show
  1. package/README.md +101 -1
  2. package/package.json +9 -3
  3. package/src/cli/config.ts +118 -2
  4. package/src/config.ts +23 -3
  5. package/src/index.ts +262 -6
  6. package/src/integrations/elasticsearch/client.ts +210 -0
  7. package/src/integrations/grafana/client.ts +186 -0
  8. package/src/integrations/kubernetes/multi-cluster.ts +199 -0
  9. package/src/integrations/kubernetes/types.ts +24 -0
  10. package/src/integrations/loki/client.ts +219 -0
  11. package/src/integrations/prometheus/client.ts +163 -0
  12. package/src/integrations/slack/client.ts +265 -0
  13. package/src/integrations/teams/client.ts +199 -0
  14. package/src/mastra/agents/debugger.ts +164 -109
  15. package/src/mastra/index.ts +2 -2
  16. package/src/mastra/tools/approval-store.ts +180 -0
  17. package/src/mastra/tools/cli.ts +94 -2
  18. package/src/mastra/tools/cost.ts +389 -0
  19. package/src/mastra/tools/logs.ts +210 -0
  20. package/src/mastra/tools/network.ts +253 -0
  21. package/src/mastra/tools/prometheus.ts +221 -0
  22. package/src/mastra/tools/remediation.ts +365 -0
  23. package/src/mastra/tools/runbook.ts +186 -0
  24. package/src/sandbox/bashlet.ts +76 -10
  25. package/src/server/routes/history.ts +207 -0
  26. package/src/server/routes/notifications.ts +236 -0
  27. package/src/server/webhook.ts +36 -2
  28. package/src/storage/index.ts +3 -0
  29. package/src/storage/investigation-history.ts +277 -0
  30. package/src/storage/runbook-index.ts +330 -0
  31. package/src/storage/types.ts +72 -0
  32. package/src/tui/app.tsx +278 -197
  33. package/src/tui/components/approval-dialog.tsx +147 -0
  34. package/src/tui/components/approval-modal.tsx +278 -0
  35. package/src/tui/components/centered-layout.tsx +33 -0
  36. package/src/tui/components/editor.tsx +87 -0
  37. package/src/tui/components/header.tsx +53 -0
  38. package/src/tui/components/index.ts +55 -0
  39. package/src/tui/components/message-item.tsx +131 -0
  40. package/src/tui/components/messages-panel.tsx +71 -0
  41. package/src/tui/components/status-badge.tsx +20 -0
  42. package/src/tui/components/status-bar.tsx +39 -0
  43. package/src/tui/components/styled-span.tsx +24 -0
  44. package/src/tui/components/timeline.tsx +223 -0
  45. package/src/tui/components/toast.tsx +104 -0
  46. package/src/tui/theme/index.ts +21 -0
  47. package/src/tui/theme/tokens.ts +180 -0
@@ -0,0 +1,147 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import { DialogProvider, useDialog, useDialogKeyboard, type ConfirmContext } from "@opentui-ui/dialog/solid";
3
+ import type { JSX, ParentProps } from "solid-js";
4
+ import {
5
+ colors,
6
+ spacing,
7
+ ATTR_BOLD,
8
+ ATTR_DIM,
9
+ getRiskColor,
10
+ getRiskHexColor,
11
+ type RiskLevel,
12
+ } from "../theme/index.js";
13
+
14
+ export type { RiskLevel };
15
+
16
+ export interface ApprovalDialogOptions {
17
+ command: string;
18
+ riskLevel: RiskLevel;
19
+ description?: string;
20
+ }
21
+
22
+ function getRiskEmoji(risk: RiskLevel): string {
23
+ switch (risk) {
24
+ case "low":
25
+ return "●";
26
+ case "medium":
27
+ return "●";
28
+ case "high":
29
+ return "●";
30
+ case "critical":
31
+ return "●";
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Content component for the approval confirmation dialog
37
+ */
38
+ function ApprovalDialogContent(ctx: ConfirmContext & { options: ApprovalDialogOptions }) {
39
+ const { command, riskLevel, description } = ctx.options;
40
+ const riskColor = getRiskColor(riskLevel);
41
+
42
+ // Handle keyboard input
43
+ useDialogKeyboard((key) => {
44
+ if (key.name === "return" || key.name === "y") {
45
+ ctx.resolve(true);
46
+ } else if (key.name === "escape" || key.name === "n") {
47
+ ctx.resolve(false);
48
+ }
49
+ }, ctx.dialogId);
50
+
51
+ return () => (
52
+ <box flexDirection="column" gap={1}>
53
+ {/* Header */}
54
+ <box flexDirection="row" gap={1}>
55
+ <text fg={riskColor}>{getRiskEmoji(riskLevel)}</text>
56
+ <text fg={colors.text.primary} attributes={ATTR_BOLD}>
57
+ Write Operation Requires Approval
58
+ </text>
59
+ <text fg={colors.text.secondary} attributes={ATTR_DIM}>
60
+ ({riskLevel} risk)
61
+ </text>
62
+ </box>
63
+
64
+ {/* Command */}
65
+ <box flexDirection="column">
66
+ <text fg={colors.info} attributes={ATTR_BOLD}>Command:</text>
67
+ <text fg={colors.warning}>{command}</text>
68
+ </box>
69
+
70
+ {/* Description if provided */}
71
+ {description && (
72
+ <box flexDirection="column">
73
+ <text fg={colors.text.secondary} attributes={ATTR_DIM}>{description}</text>
74
+ </box>
75
+ )}
76
+
77
+ {/* Warning for high risk */}
78
+ {(riskLevel === "high" || riskLevel === "critical") && (
79
+ <box>
80
+ <text fg={colors.error} attributes={ATTR_BOLD}>
81
+ This is a {riskLevel}-risk operation. Review carefully.
82
+ </text>
83
+ </box>
84
+ )}
85
+
86
+ {/* Actions */}
87
+ <box flexDirection="row" gap={4} marginTop={1}>
88
+ <text fg={colors.success} attributes={ATTR_BOLD}>[Y] Approve</text>
89
+ <text fg={colors.error} attributes={ATTR_BOLD}>[N] Reject</text>
90
+ </box>
91
+
92
+ {/* Instructions */}
93
+ <text fg={colors.text.secondary} attributes={ATTR_DIM}>
94
+ Press Y to approve, N to reject, or Esc to cancel
95
+ </text>
96
+ </box>
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Provider component that enables dialog functionality
102
+ */
103
+ export function ApprovalDialogProvider(props: ParentProps): JSX.Element {
104
+ return (
105
+ <DialogProvider
106
+ size="medium"
107
+ dialogOptions={{
108
+ style: {
109
+ borderStyle: "single",
110
+ borderColor: colors.text.primary,
111
+ backgroundColor: colors.background.primary,
112
+ paddingLeft: spacing.sm,
113
+ paddingRight: spacing.sm,
114
+ paddingTop: 1,
115
+ paddingBottom: 1,
116
+ },
117
+ }}
118
+ backdropColor="#000000"
119
+ backdropOpacity={0.5}
120
+ >
121
+ {props.children}
122
+ </DialogProvider>
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Hook to show approval confirmation dialogs
128
+ */
129
+ export function useApprovalDialog() {
130
+ const dialog = useDialog();
131
+
132
+ const showApproval = async (options: ApprovalDialogOptions): Promise<boolean> => {
133
+ const result = await dialog.confirm({
134
+ content: (ctx) => ApprovalDialogContent({ ...ctx, options }),
135
+ style: {
136
+ borderColor: getRiskHexColor(options.riskLevel),
137
+ borderStyle: "double",
138
+ },
139
+ });
140
+ return result;
141
+ };
142
+
143
+ return { showApproval, ...dialog };
144
+ }
145
+
146
+ // Re-export dialog hooks for other uses
147
+ export { useDialog, useDialogKeyboard };
@@ -0,0 +1,278 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import { Show, type JSX } from "solid-js";
3
+ import { createTextAttributes } from "@opentui/core";
4
+
5
+ const ATTR_DIM = createTextAttributes({ dim: true });
6
+ const ATTR_BOLD = createTextAttributes({ bold: true });
7
+
8
+ export interface ApprovalRequest {
9
+ id: string;
10
+ action: string;
11
+ target: string;
12
+ riskLevel: "low" | "medium" | "high" | "critical";
13
+ description: string;
14
+ approvalToken: string;
15
+ expiresAt: Date;
16
+ }
17
+
18
+ interface ApprovalModalProps {
19
+ request: ApprovalRequest | null;
20
+ onApprove: (token: string) => void;
21
+ onReject: () => void;
22
+ visible: boolean;
23
+ }
24
+
25
+ function getRiskColor(risk: ApprovalRequest["riskLevel"]): string {
26
+ switch (risk) {
27
+ case "low":
28
+ return "green";
29
+ case "medium":
30
+ return "yellow";
31
+ case "high":
32
+ return "red";
33
+ case "critical":
34
+ return "magenta";
35
+ }
36
+ }
37
+
38
+ function getRiskEmoji(risk: ApprovalRequest["riskLevel"]): string {
39
+ switch (risk) {
40
+ case "low":
41
+ return "🟢";
42
+ case "medium":
43
+ return "🟡";
44
+ case "high":
45
+ return "🟠";
46
+ case "critical":
47
+ return "🔴";
48
+ }
49
+ }
50
+
51
+ export function ApprovalModal(props: ApprovalModalProps): JSX.Element {
52
+ const request = () => props.request;
53
+ const visible = () => props.visible && request() !== null;
54
+
55
+ return (
56
+ <Show when={visible()}>
57
+ <box
58
+ position="absolute"
59
+ top={5}
60
+ left={10}
61
+ width={60}
62
+ borderStyle="double"
63
+ borderColor={getRiskColor(request()!.riskLevel)}
64
+ paddingLeft={2}
65
+ paddingRight={2}
66
+ paddingTop={1}
67
+ paddingBottom={1}
68
+ flexDirection="column"
69
+ >
70
+ {/* Header */}
71
+ <box flexDirection="row" justifyContent="center" marginBottom={1}>
72
+ <text fg={getRiskColor(request()!.riskLevel)} attributes={ATTR_BOLD}>
73
+ ⚠️ APPROVAL REQUIRED ⚠️
74
+ </text>
75
+ </box>
76
+
77
+ {/* Risk Level */}
78
+ <box flexDirection="row" gap={1} marginBottom={1}>
79
+ <text fg="white">Risk Level:</text>
80
+ <text fg={getRiskColor(request()!.riskLevel)} attributes={ATTR_BOLD}>
81
+ {getRiskEmoji(request()!.riskLevel)} {request()!.riskLevel.toUpperCase()}
82
+ </text>
83
+ </box>
84
+
85
+ {/* Action */}
86
+ <box flexDirection="column" marginBottom={1}>
87
+ <text fg="cyan" attributes={ATTR_BOLD}>Action:</text>
88
+ <text fg="white">{request()!.action}</text>
89
+ </box>
90
+
91
+ {/* Target */}
92
+ <box flexDirection="column" marginBottom={1}>
93
+ <text fg="cyan" attributes={ATTR_BOLD}>Target:</text>
94
+ <text fg="white">{request()!.target}</text>
95
+ </box>
96
+
97
+ {/* Description */}
98
+ <box flexDirection="column" marginBottom={1}>
99
+ <text fg="cyan" attributes={ATTR_BOLD}>Description:</text>
100
+ <text fg="gray" wrapMode="word">{request()!.description}</text>
101
+ </box>
102
+
103
+ {/* Expiration */}
104
+ <box flexDirection="row" gap={1} marginBottom={1}>
105
+ <text fg="gray" attributes={ATTR_DIM}>
106
+ Expires: {formatTimeRemaining(request()!.expiresAt)}
107
+ </text>
108
+ </box>
109
+
110
+ {/* Divider */}
111
+ <text fg="gray" attributes={ATTR_DIM}>
112
+ ─────────────────────────────────────────────
113
+ </text>
114
+
115
+ {/* Actions */}
116
+ <box flexDirection="row" justifyContent="center" gap={4} marginTop={1}>
117
+ <text fg="green" attributes={ATTR_BOLD}>
118
+ [Y] Approve
119
+ </text>
120
+ <text fg="red" attributes={ATTR_BOLD}>
121
+ [N] Reject
122
+ </text>
123
+ </box>
124
+
125
+ {/* Warning for high risk */}
126
+ <Show when={request()!.riskLevel === "high" || request()!.riskLevel === "critical"}>
127
+ <box marginTop={1}>
128
+ <text fg="red" attributes={ATTR_BOLD}>
129
+ ⚠️ This is a {request()!.riskLevel}-risk action. Please review carefully.
130
+ </text>
131
+ </box>
132
+ </Show>
133
+ </box>
134
+ </Show>
135
+ );
136
+ }
137
+
138
+ function formatTimeRemaining(expiresAt: Date): string {
139
+ const remaining = expiresAt.getTime() - Date.now();
140
+ if (remaining <= 0) {
141
+ return "Expired";
142
+ }
143
+
144
+ const minutes = Math.floor(remaining / 60000);
145
+ const seconds = Math.floor((remaining % 60000) / 1000);
146
+
147
+ if (minutes > 0) {
148
+ return `${minutes}m ${seconds}s`;
149
+ }
150
+ return `${seconds}s`;
151
+ }
152
+
153
+ // Interactive approval prompt for use in message flow
154
+ // Similar to Claude Code's AskUserQuestion pattern
155
+ interface CommandApprovalProps {
156
+ approvalId: string;
157
+ command: string;
158
+ riskLevel: ApprovalRequest["riskLevel"];
159
+ selectedOption: number; // 0 = approve, 1 = reject, -1 = no selection
160
+ onSelect: (option: number) => void;
161
+ submitted: boolean;
162
+ }
163
+
164
+ export function CommandApproval(props: CommandApprovalProps): JSX.Element {
165
+ const riskColor = getRiskColor(props.riskLevel);
166
+
167
+ return (
168
+ <box
169
+ flexDirection="column"
170
+ borderStyle="single"
171
+ borderColor={riskColor}
172
+ paddingLeft={1}
173
+ paddingRight={1}
174
+ paddingTop={1}
175
+ paddingBottom={1}
176
+ marginTop={1}
177
+ marginBottom={1}
178
+ >
179
+ {/* Header */}
180
+ <box flexDirection="row" gap={1} marginBottom={1}>
181
+ <text fg={riskColor}>{getRiskEmoji(props.riskLevel)}</text>
182
+ <text fg="white" attributes={ATTR_BOLD}>
183
+ Write Operation Requires Approval
184
+ </text>
185
+ <text fg="gray" attributes={ATTR_DIM}>
186
+ ({props.riskLevel} risk)
187
+ </text>
188
+ </box>
189
+
190
+ {/* Command */}
191
+ <box flexDirection="column" marginBottom={1}>
192
+ <text fg="cyan" attributes={ATTR_BOLD}>Command:</text>
193
+ <text fg="yellow">{props.command}</text>
194
+ </box>
195
+
196
+ {/* Options - Claude Code style */}
197
+ <box flexDirection="column" gap={1}>
198
+ <OptionButton
199
+ index={0}
200
+ label="Yes, execute this command"
201
+ selected={props.selectedOption === 0}
202
+ submitted={props.submitted}
203
+ color="green"
204
+ />
205
+ <OptionButton
206
+ index={1}
207
+ label="No, cancel this operation"
208
+ selected={props.selectedOption === 1}
209
+ submitted={props.submitted}
210
+ color="red"
211
+ />
212
+ </box>
213
+
214
+ {/* Instructions */}
215
+ <Show when={!props.submitted}>
216
+ <text fg="gray" attributes={ATTR_DIM} marginTop={1}>
217
+ Use ↑↓ to select, Enter to confirm
218
+ </text>
219
+ </Show>
220
+ </box>
221
+ );
222
+ }
223
+
224
+ interface OptionButtonProps {
225
+ index: number;
226
+ label: string;
227
+ selected: boolean;
228
+ submitted: boolean;
229
+ color: string;
230
+ }
231
+
232
+ function OptionButton(props: OptionButtonProps): JSX.Element {
233
+ const isSelected = () => props.selected;
234
+ const isSubmitted = () => props.submitted;
235
+
236
+ return (
237
+ <box flexDirection="row" gap={1}>
238
+ <Show
239
+ when={isSelected()}
240
+ fallback={<text fg="gray">○</text>}
241
+ >
242
+ <text fg={props.color}>●</text>
243
+ </Show>
244
+ <text
245
+ fg={isSelected() ? props.color : "white"}
246
+ attributes={isSelected() ? ATTR_BOLD : undefined}
247
+ >
248
+ {props.label}
249
+ </text>
250
+ <Show when={isSelected() && isSubmitted()}>
251
+ <text fg={props.color}> ✓</text>
252
+ </Show>
253
+ </box>
254
+ );
255
+ }
256
+
257
+ // Compact inline version for showing result after approval
258
+ interface ApprovalResultProps {
259
+ command: string;
260
+ approved: boolean;
261
+ riskLevel: ApprovalRequest["riskLevel"];
262
+ }
263
+
264
+ export function ApprovalResult(props: ApprovalResultProps): JSX.Element {
265
+ const riskColor = getRiskColor(props.riskLevel);
266
+ const statusColor = props.approved ? "green" : "red";
267
+ const statusText = props.approved ? "Approved" : "Rejected";
268
+ const statusIcon = props.approved ? "✓" : "✗";
269
+
270
+ return (
271
+ <box flexDirection="row" gap={1} marginTop={1} marginBottom={1}>
272
+ <text fg={riskColor}>{getRiskEmoji(props.riskLevel)}</text>
273
+ <text fg="gray" attributes={ATTR_DIM}>[{props.riskLevel}]</text>
274
+ <text fg={statusColor}>{statusIcon} {statusText}:</text>
275
+ <text fg="yellow">{props.command}</text>
276
+ </box>
277
+ );
278
+ }
@@ -0,0 +1,33 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import type { JSX, ParentProps } from "solid-js";
3
+ import { layout } from "../theme/index.js";
4
+
5
+ export interface CenteredLayoutProps extends ParentProps {
6
+ maxWidth?: number;
7
+ }
8
+
9
+ /**
10
+ * CenteredLayout - Wrapper component providing centered max-width container
11
+ * Creates a centered layout with optional max-width constraint
12
+ */
13
+ export function CenteredLayout(props: CenteredLayoutProps): JSX.Element {
14
+ const maxWidth = props.maxWidth ?? layout.maxWidth;
15
+
16
+ return (
17
+ <box
18
+ flexDirection="row"
19
+ justifyContent="center"
20
+ width="100%"
21
+ height="100%"
22
+ >
23
+ <box
24
+ flexDirection="column"
25
+ width="100%"
26
+ height="100%"
27
+ maxWidth={maxWidth}
28
+ >
29
+ {props.children}
30
+ </box>
31
+ </box>
32
+ );
33
+ }
@@ -0,0 +1,87 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import { Show } from "solid-js";
3
+ import type { JSX } from "solid-js";
4
+ import { colors, spacing, ATTR_BOLD, ATTR_DIM, type AppStatus } from "../theme/index.js";
5
+
6
+ export interface EditorProps {
7
+ status: AppStatus;
8
+ value: string;
9
+ onInput: (value: string) => void;
10
+ onSubmit: (value: string) => void;
11
+ onApprovalInput?: (value: string) => void;
12
+ onApprovalKeyDown?: (key: { name: string }) => void;
13
+ }
14
+
15
+ /**
16
+ * Editor - Enhanced input with cyan prompt indicator
17
+ * OpenCode-style with clean border and prompt
18
+ */
19
+ export function Editor(props: EditorProps): JSX.Element {
20
+ const getBorderColor = () => {
21
+ if (props.status === "awaiting_approval") return colors.warning;
22
+ if (props.status === "investigating") return colors.warning;
23
+ return colors.info;
24
+ };
25
+
26
+ return (
27
+ <Show
28
+ when={props.status === "awaiting_approval"}
29
+ fallback={
30
+ <box
31
+ borderStyle="single"
32
+ borderColor={getBorderColor()}
33
+ paddingLeft={spacing.xs}
34
+ paddingRight={spacing.xs}
35
+ flexDirection="row"
36
+ gap={1}
37
+ >
38
+ <text fg={colors.info} attributes={ATTR_BOLD}>
39
+ {">"}
40
+ </text>
41
+ <input
42
+ flexGrow={1}
43
+ focused={true}
44
+ value={props.value}
45
+ onInput={props.onInput}
46
+ onSubmit={props.onSubmit}
47
+ placeholder={
48
+ props.status === "investigating"
49
+ ? "Investigating..."
50
+ : "Describe the incident..."
51
+ }
52
+ textColor={colors.text.primary}
53
+ placeholderColor={colors.text.secondary}
54
+ focusedTextColor={colors.text.primary}
55
+ focusedBackgroundColor={colors.background.primary}
56
+ />
57
+ </box>
58
+ }
59
+ >
60
+ {/* Approval mode input */}
61
+ <box
62
+ borderStyle="double"
63
+ borderColor={colors.warning}
64
+ paddingLeft={spacing.xs}
65
+ paddingRight={spacing.xs}
66
+ flexDirection="row"
67
+ gap={1}
68
+ >
69
+ <text fg={colors.warning} attributes={ATTR_BOLD}>
70
+ ?
71
+ </text>
72
+ <input
73
+ flexGrow={1}
74
+ focused={true}
75
+ value=""
76
+ onInput={(value) => props.onApprovalInput?.(value)}
77
+ onKeyDown={(key) => props.onApprovalKeyDown?.(key)}
78
+ placeholder="Press Y to approve, N to reject"
79
+ textColor={colors.text.primary}
80
+ placeholderColor={colors.text.secondary}
81
+ focusedTextColor={colors.text.primary}
82
+ focusedBackgroundColor={colors.background.primary}
83
+ />
84
+ </box>
85
+ </Show>
86
+ );
87
+ }
@@ -0,0 +1,53 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import type { JSX } from "solid-js";
3
+ import { colors, spacing, ATTR_BOLD, ATTR_DIM, type AppStatus } from "../theme/index.js";
4
+ import { StatusBadge } from "./status-badge.js";
5
+
6
+ export interface HeaderProps {
7
+ status: AppStatus;
8
+ currentTool?: string | null;
9
+ kubeContext: string;
10
+ modelName?: string;
11
+ }
12
+
13
+ /**
14
+ * Header - Top section with logo, title, model info, status badge, and kubernetes context
15
+ * OpenCode-style layout with clean visual hierarchy
16
+ */
17
+ export function Header(props: HeaderProps): JSX.Element {
18
+ return (
19
+ <box
20
+ borderStyle="single"
21
+ borderColor={colors.primary}
22
+ paddingLeft={spacing.sm}
23
+ paddingRight={spacing.sm}
24
+ paddingTop={0}
25
+ paddingBottom={0}
26
+ flexDirection="column"
27
+ >
28
+ {/* Top row: Logo + Title + Model | Status Badge */}
29
+ <box flexDirection="row" justifyContent="space-between" paddingTop={1}>
30
+ <box flexDirection="row" gap={1}>
31
+ <text fg={colors.primary} attributes={ATTR_BOLD}>
32
+ ☸ TRIAGENT
33
+ </text>
34
+ <text fg={colors.text.secondary}>|</text>
35
+ <text fg={colors.text.secondary} attributes={ATTR_DIM}>
36
+ {props.modelName}
37
+ </text>
38
+ </box>
39
+ <StatusBadge status={props.status} currentTool={props.currentTool} />
40
+ </box>
41
+
42
+ {/* Bottom row: Subtitle | Kubernetes context */}
43
+ <box flexDirection="row" justifyContent="space-between" paddingBottom={1}>
44
+ <text fg={colors.text.secondary} attributes={ATTR_DIM}>
45
+ Kubernetes Debugging Agent
46
+ </text>
47
+ <text fg={colors.info}>
48
+ cluster: {props.kubeContext}
49
+ </text>
50
+ </box>
51
+ </box>
52
+ );
53
+ }
@@ -0,0 +1,55 @@
1
+ // TUI Components - OpenTUI UI integration
2
+
3
+ // Layout components
4
+ export { CenteredLayout, type CenteredLayoutProps } from "./centered-layout.js";
5
+
6
+ // Header components
7
+ export { Header, type HeaderProps } from "./header.js";
8
+ export { StatusBadge, type StatusBadgeProps } from "./status-badge.js";
9
+
10
+ // Message components
11
+ export { MessageItem, type Message, type MessageItemProps } from "./message-item.js";
12
+ export { MessagesPanel, type MessagesPanelProps } from "./messages-panel.js";
13
+
14
+ // Input components
15
+ export { Editor, type EditorProps } from "./editor.js";
16
+
17
+ // Status components
18
+ export { StatusBar, type StatusBarProps } from "./status-bar.js";
19
+
20
+ // Styled span with proper typing for fg/bg/attributes
21
+ export { StyledSpan, type StyledSpanProps } from "./styled-span.js";
22
+
23
+ // Toast notifications
24
+ export {
25
+ ToastProvider,
26
+ toast,
27
+ toastSuccess,
28
+ toastError,
29
+ toastWarning,
30
+ toastInfo,
31
+ toastLoading,
32
+ toastDismiss,
33
+ toastPromise,
34
+ useToasts,
35
+ TOAST_DURATION,
36
+ } from "./toast.js";
37
+
38
+ // Approval dialogs
39
+ export {
40
+ ApprovalDialogProvider,
41
+ useApprovalDialog,
42
+ useDialog,
43
+ useDialogKeyboard,
44
+ type RiskLevel,
45
+ type ApprovalDialogOptions,
46
+ } from "./approval-dialog.js";
47
+
48
+ // Existing components
49
+ export { ApprovalModal, CommandApproval, ApprovalResult, type ApprovalRequest } from "./approval-modal.js";
50
+ export {
51
+ Timeline,
52
+ CompactTimeline,
53
+ investigationEventsToTimeline,
54
+ type TimelineEvent,
55
+ } from "./timeline.js";