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.
- package/README.md +101 -1
- package/package.json +9 -3
- package/src/cli/config.ts +118 -2
- package/src/config.ts +23 -3
- package/src/index.ts +262 -6
- package/src/integrations/elasticsearch/client.ts +210 -0
- package/src/integrations/grafana/client.ts +186 -0
- package/src/integrations/kubernetes/multi-cluster.ts +199 -0
- package/src/integrations/kubernetes/types.ts +24 -0
- package/src/integrations/loki/client.ts +219 -0
- package/src/integrations/prometheus/client.ts +163 -0
- package/src/integrations/slack/client.ts +265 -0
- package/src/integrations/teams/client.ts +199 -0
- package/src/mastra/agents/debugger.ts +164 -109
- package/src/mastra/index.ts +2 -2
- package/src/mastra/tools/approval-store.ts +180 -0
- package/src/mastra/tools/cli.ts +94 -2
- package/src/mastra/tools/cost.ts +389 -0
- package/src/mastra/tools/logs.ts +210 -0
- package/src/mastra/tools/network.ts +253 -0
- package/src/mastra/tools/prometheus.ts +221 -0
- package/src/mastra/tools/remediation.ts +365 -0
- package/src/mastra/tools/runbook.ts +186 -0
- package/src/sandbox/bashlet.ts +76 -10
- package/src/server/routes/history.ts +207 -0
- package/src/server/routes/notifications.ts +236 -0
- package/src/server/webhook.ts +36 -2
- package/src/storage/index.ts +3 -0
- package/src/storage/investigation-history.ts +277 -0
- package/src/storage/runbook-index.ts +330 -0
- package/src/storage/types.ts +72 -0
- package/src/tui/app.tsx +278 -197
- package/src/tui/components/approval-dialog.tsx +147 -0
- package/src/tui/components/approval-modal.tsx +278 -0
- package/src/tui/components/centered-layout.tsx +33 -0
- package/src/tui/components/editor.tsx +87 -0
- package/src/tui/components/header.tsx +53 -0
- package/src/tui/components/index.ts +55 -0
- package/src/tui/components/message-item.tsx +131 -0
- package/src/tui/components/messages-panel.tsx +71 -0
- package/src/tui/components/status-badge.tsx +20 -0
- package/src/tui/components/status-bar.tsx +39 -0
- package/src/tui/components/styled-span.tsx +24 -0
- package/src/tui/components/timeline.tsx +223 -0
- package/src/tui/components/toast.tsx +104 -0
- package/src/tui/theme/index.ts +21 -0
- 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";
|