@pablozaiden/terminatui 0.2.0 → 0.3.0-beta-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.
- package/AGENTS.md +14 -2
- package/CLAUDE.md +1 -0
- package/README.md +64 -43
- package/bun.lock +85 -0
- package/examples/tui-app/commands/config/app/get.ts +6 -10
- package/examples/tui-app/commands/config/app/index.ts +2 -6
- package/examples/tui-app/commands/config/app/set.ts +23 -13
- package/examples/tui-app/commands/config/index.ts +2 -6
- package/examples/tui-app/commands/config/user/get.ts +6 -10
- package/examples/tui-app/commands/config/user/index.ts +2 -6
- package/examples/tui-app/commands/config/user/set.ts +6 -10
- package/examples/tui-app/commands/greet.ts +13 -11
- package/examples/tui-app/commands/math.ts +5 -9
- package/examples/tui-app/commands/status.ts +21 -12
- package/examples/tui-app/index.ts +6 -3
- package/guides/01-hello-world.md +7 -2
- package/guides/02-adding-options.md +2 -2
- package/guides/03-multiple-commands.md +6 -8
- package/guides/04-subcommands.md +8 -8
- package/guides/05-interactive-tui.md +45 -30
- package/guides/06-config-validation.md +4 -12
- package/guides/07-async-cancellation.md +14 -16
- package/guides/08-complete-application.md +12 -42
- package/guides/README.md +7 -3
- package/package.json +4 -8
- package/src/__tests__/application.test.ts +87 -68
- package/src/__tests__/buildCliCommand.test.ts +99 -119
- package/src/__tests__/builtins.test.ts +27 -75
- package/src/__tests__/command.test.ts +100 -131
- package/src/__tests__/context.test.ts +1 -26
- package/src/__tests__/helpCore.test.ts +227 -0
- package/src/__tests__/parser.test.ts +98 -244
- package/src/__tests__/registry.test.ts +33 -160
- package/src/__tests__/schemaToFields.test.ts +75 -158
- package/src/builtins/help.ts +12 -4
- package/src/builtins/settings.ts +18 -32
- package/src/builtins/version.ts +4 -4
- package/src/cli/output/colors.ts +1 -1
- package/src/cli/parser.ts +26 -95
- package/src/core/application.ts +192 -110
- package/src/core/command.ts +26 -9
- package/src/core/context.ts +31 -20
- package/src/core/help.ts +24 -18
- package/src/core/knownCommands.ts +13 -0
- package/src/core/logger.ts +39 -42
- package/src/core/registry.ts +5 -12
- package/src/tui/TuiApplication.tsx +63 -120
- package/src/tui/TuiRoot.tsx +135 -0
- package/src/tui/adapters/factory.ts +19 -0
- package/src/tui/adapters/ink/InkRenderer.tsx +135 -0
- package/src/tui/adapters/ink/components/Button.tsx +12 -0
- package/src/tui/adapters/ink/components/Code.tsx +6 -0
- package/src/tui/adapters/ink/components/CodeHighlight.tsx +6 -0
- package/src/tui/adapters/ink/components/Container.tsx +5 -0
- package/src/tui/adapters/ink/components/Field.tsx +12 -0
- package/src/tui/adapters/ink/components/Label.tsx +24 -0
- package/src/tui/adapters/ink/components/MenuButton.tsx +12 -0
- package/src/tui/adapters/ink/components/MenuItem.tsx +17 -0
- package/src/tui/adapters/ink/components/Overlay.tsx +5 -0
- package/src/tui/adapters/ink/components/Panel.tsx +15 -0
- package/src/tui/adapters/ink/components/ScrollView.tsx +5 -0
- package/src/tui/adapters/ink/components/Select.tsx +44 -0
- package/src/tui/adapters/ink/components/Spacer.tsx +15 -0
- package/src/tui/adapters/ink/components/Spinner.tsx +5 -0
- package/src/tui/adapters/ink/components/TextInput.tsx +22 -0
- package/src/tui/adapters/ink/components/Value.tsx +7 -0
- package/src/tui/adapters/ink/keyboard.ts +97 -0
- package/src/tui/adapters/ink/utils.ts +16 -0
- package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +115 -0
- package/src/tui/adapters/opentui/components/Button.tsx +13 -0
- package/src/tui/adapters/opentui/components/Code.tsx +12 -0
- package/src/tui/adapters/opentui/components/CodeHighlight.tsx +24 -0
- package/src/tui/adapters/opentui/components/Container.tsx +56 -0
- package/src/tui/adapters/opentui/components/Field.tsx +18 -0
- package/src/tui/adapters/opentui/components/Label.tsx +15 -0
- package/src/tui/adapters/opentui/components/MenuButton.tsx +14 -0
- package/src/tui/adapters/opentui/components/MenuItem.tsx +29 -0
- package/src/tui/adapters/opentui/components/Overlay.tsx +21 -0
- package/src/tui/adapters/opentui/components/Panel.tsx +78 -0
- package/src/tui/adapters/opentui/components/ScrollView.tsx +85 -0
- package/src/tui/adapters/opentui/components/Select.tsx +59 -0
- package/src/tui/adapters/opentui/components/Spacer.tsx +5 -0
- package/src/tui/adapters/opentui/components/Spinner.tsx +12 -0
- package/src/tui/adapters/opentui/components/TextInput.tsx +13 -0
- package/src/tui/adapters/opentui/components/Value.tsx +13 -0
- package/src/tui/{hooks → adapters/opentui/hooks}/useSpinner.ts +2 -11
- package/src/tui/adapters/opentui/keyboard.ts +61 -0
- package/src/tui/adapters/types.ts +70 -0
- package/src/tui/components/ActionButton.tsx +0 -36
- package/src/tui/components/CommandSelector.tsx +45 -92
- package/src/tui/components/ConfigForm.tsx +68 -42
- package/src/tui/components/FieldRow.tsx +0 -30
- package/src/tui/components/Header.tsx +14 -13
- package/src/tui/components/JsonHighlight.tsx +10 -17
- package/src/tui/components/ModalBase.tsx +38 -0
- package/src/tui/components/ResultsPanel.tsx +27 -36
- package/src/tui/components/StatusBar.tsx +24 -39
- package/src/tui/components/logColors.ts +12 -0
- package/src/tui/context/ClipboardContext.tsx +87 -0
- package/src/tui/context/ExecutorContext.tsx +139 -0
- package/src/tui/context/KeyboardContext.tsx +85 -71
- package/src/tui/context/LogsContext.tsx +35 -0
- package/src/tui/context/NavigationContext.tsx +194 -0
- package/src/tui/context/RendererContext.tsx +20 -0
- package/src/tui/context/TuiAppContext.tsx +58 -0
- package/src/tui/hooks/useActiveKeyHandler.ts +75 -0
- package/src/tui/hooks/useBackHandler.ts +34 -0
- package/src/tui/hooks/useClipboard.ts +40 -25
- package/src/tui/hooks/useClipboardProvider.ts +42 -0
- package/src/tui/hooks/useGlobalKeyHandler.ts +54 -0
- package/src/tui/modals/CliModal.tsx +82 -0
- package/src/tui/modals/EditorModal.tsx +207 -0
- package/src/tui/modals/LogsModal.tsx +98 -0
- package/src/tui/registry.ts +102 -0
- package/src/tui/screens/CommandSelectScreen.tsx +162 -0
- package/src/tui/screens/ConfigScreen.tsx +160 -0
- package/src/tui/screens/ErrorScreen.tsx +58 -0
- package/src/tui/screens/ResultsScreen.tsx +60 -0
- package/src/tui/screens/RunningScreen.tsx +72 -0
- package/src/tui/screens/ScreenBase.ts +6 -0
- package/src/tui/semantic/Button.tsx +7 -0
- package/src/tui/semantic/Code.tsx +7 -0
- package/src/tui/semantic/CodeHighlight.tsx +7 -0
- package/src/tui/semantic/Container.tsx +7 -0
- package/src/tui/semantic/Field.tsx +7 -0
- package/src/tui/semantic/Label.tsx +7 -0
- package/src/tui/semantic/MenuButton.tsx +7 -0
- package/src/tui/semantic/MenuItem.tsx +7 -0
- package/src/tui/semantic/Overlay.tsx +7 -0
- package/src/tui/semantic/Panel.tsx +7 -0
- package/src/tui/semantic/ScrollView.tsx +9 -0
- package/src/tui/semantic/Select.tsx +7 -0
- package/src/tui/semantic/Spacer.tsx +7 -0
- package/src/tui/semantic/Spinner.tsx +7 -0
- package/src/tui/semantic/TextInput.tsx +7 -0
- package/src/tui/semantic/Value.tsx +7 -0
- package/src/tui/semantic/types.ts +195 -0
- package/src/tui/theme.ts +25 -14
- package/src/tui/utils/buildCliCommand.ts +1 -0
- package/src/tui/utils/getEnumKeys.ts +3 -0
- package/src/tui/utils/parameterPersistence.ts +1 -0
- package/src/types/command.ts +0 -60
- package/examples/tui-app/commands/index.ts +0 -4
- package/src/__tests__/colors.test.ts +0 -127
- package/src/__tests__/commandClass.test.ts +0 -130
- package/src/__tests__/help.test.ts +0 -412
- package/src/__tests__/registryNew.test.ts +0 -160
- package/src/__tests__/table.test.ts +0 -146
- package/src/__tests__/tui.test.ts +0 -26
- package/src/builtins/index.ts +0 -4
- package/src/cli/help.ts +0 -174
- package/src/cli/index.ts +0 -3
- package/src/cli/output/index.ts +0 -2
- package/src/cli/output/table.ts +0 -141
- package/src/commands/help.ts +0 -50
- package/src/commands/index.ts +0 -1
- package/src/components/index.ts +0 -147
- package/src/core/index.ts +0 -15
- package/src/hooks/index.ts +0 -131
- package/src/index.ts +0 -137
- package/src/registry/commandRegistry.ts +0 -77
- package/src/registry/index.ts +0 -1
- package/src/tui/TuiApp.tsx +0 -619
- package/src/tui/app.ts +0 -29
- package/src/tui/components/CliModal.tsx +0 -81
- package/src/tui/components/EditorModal.tsx +0 -177
- package/src/tui/components/LogsPanel.tsx +0 -86
- package/src/tui/components/index.ts +0 -13
- package/src/tui/context/index.ts +0 -7
- package/src/tui/hooks/index.ts +0 -35
- package/src/tui/hooks/useKeyboardHandler.ts +0 -91
- package/src/tui/hooks/useLogStream.ts +0 -96
- package/src/tui/index.ts +0 -65
- package/src/tui/utils/index.ts +0 -13
- package/src/types/index.ts +0 -1
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { Theme } from "../theme.ts";
|
|
2
|
-
import { useKeyboardHandler, KeyboardPriority } from "../hooks/useKeyboardHandler.ts";
|
|
3
|
-
|
|
4
|
-
interface CliModalProps {
|
|
5
|
-
/** CLI command to display */
|
|
6
|
-
command: string;
|
|
7
|
-
/** Whether the modal is visible */
|
|
8
|
-
visible: boolean;
|
|
9
|
-
/** Called when the modal should close */
|
|
10
|
-
onClose: () => void;
|
|
11
|
-
/** Called when the command should be copied */
|
|
12
|
-
onCopy?: (content: string, label: string) => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Modal displaying the CLI command equivalent of the current config.
|
|
17
|
-
*/
|
|
18
|
-
export function CliModal({
|
|
19
|
-
command,
|
|
20
|
-
visible,
|
|
21
|
-
onClose,
|
|
22
|
-
onCopy,
|
|
23
|
-
}: CliModalProps) {
|
|
24
|
-
// Modal keyboard handler
|
|
25
|
-
useKeyboardHandler(
|
|
26
|
-
(event) => {
|
|
27
|
-
const { key } = event;
|
|
28
|
-
|
|
29
|
-
if (key.name === "escape" || key.name === "return" || key.name === "enter") {
|
|
30
|
-
onClose();
|
|
31
|
-
event.stopPropagation();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Y to copy
|
|
36
|
-
if (key.name === "y") {
|
|
37
|
-
onCopy?.(command, "CLI command");
|
|
38
|
-
event.stopPropagation();
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
KeyboardPriority.Modal,
|
|
43
|
-
{ enabled: visible, modal: true }
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (!visible) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<box
|
|
52
|
-
position="absolute"
|
|
53
|
-
top={4}
|
|
54
|
-
left={4}
|
|
55
|
-
width="80%"
|
|
56
|
-
height={10}
|
|
57
|
-
backgroundColor={Theme.overlay}
|
|
58
|
-
border={true}
|
|
59
|
-
borderStyle="rounded"
|
|
60
|
-
borderColor={Theme.overlayTitle}
|
|
61
|
-
padding={1}
|
|
62
|
-
flexDirection="column"
|
|
63
|
-
gap={1}
|
|
64
|
-
zIndex={20}
|
|
65
|
-
>
|
|
66
|
-
<text fg={Theme.overlayTitle}>
|
|
67
|
-
<strong>CLI Command</strong>
|
|
68
|
-
</text>
|
|
69
|
-
|
|
70
|
-
<scrollbox scrollX={true} height={3}>
|
|
71
|
-
<text fg={Theme.value}>
|
|
72
|
-
{command}
|
|
73
|
-
</text>
|
|
74
|
-
</scrollbox>
|
|
75
|
-
|
|
76
|
-
<text fg={Theme.statusText}>
|
|
77
|
-
Ctrl+Y to copy • Enter or Esc to close
|
|
78
|
-
</text>
|
|
79
|
-
</box>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import type { SelectOption } from "@opentui/core";
|
|
3
|
-
import { Theme } from "../theme.ts";
|
|
4
|
-
import { useKeyboardHandler, KeyboardPriority } from "../hooks/useKeyboardHandler.ts";
|
|
5
|
-
import type { FieldConfig } from "./types.ts";
|
|
6
|
-
|
|
7
|
-
interface EditorModalProps {
|
|
8
|
-
/** The key of the field being edited */
|
|
9
|
-
fieldKey: string | null;
|
|
10
|
-
/** The current value of the field */
|
|
11
|
-
currentValue: unknown;
|
|
12
|
-
/** Whether the modal is visible */
|
|
13
|
-
visible: boolean;
|
|
14
|
-
/** Called when the user submits a new value */
|
|
15
|
-
onSubmit: (value: unknown) => void;
|
|
16
|
-
/** Called when the user cancels editing */
|
|
17
|
-
onCancel: () => void;
|
|
18
|
-
/** Field configurations */
|
|
19
|
-
fieldConfigs: FieldConfig[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Modal for editing field values.
|
|
24
|
-
* Supports text, number, enum, and boolean types.
|
|
25
|
-
*/
|
|
26
|
-
export function EditorModal({
|
|
27
|
-
fieldKey,
|
|
28
|
-
currentValue,
|
|
29
|
-
visible,
|
|
30
|
-
onSubmit,
|
|
31
|
-
onCancel,
|
|
32
|
-
fieldConfigs,
|
|
33
|
-
}: EditorModalProps) {
|
|
34
|
-
const [inputValue, setInputValue] = useState("");
|
|
35
|
-
const [selectIndex, setSelectIndex] = useState(0);
|
|
36
|
-
|
|
37
|
-
// Reset state when field changes
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (fieldKey && visible) {
|
|
40
|
-
setInputValue(String(currentValue ?? ""));
|
|
41
|
-
|
|
42
|
-
// For enums, find current index
|
|
43
|
-
const fieldConfig = fieldConfigs.find((f) => f.key === fieldKey);
|
|
44
|
-
if (fieldConfig?.options) {
|
|
45
|
-
const idx = fieldConfig.options.findIndex((o) => o.value === currentValue);
|
|
46
|
-
setSelectIndex(idx >= 0 ? idx : 0);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}, [fieldKey, currentValue, visible, fieldConfigs]);
|
|
50
|
-
|
|
51
|
-
// Modal keyboard handler - blocks all keys from bubbling out of the modal
|
|
52
|
-
useKeyboardHandler(
|
|
53
|
-
(event) => {
|
|
54
|
-
if (event.key.name === "escape") {
|
|
55
|
-
onCancel();
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
KeyboardPriority.Modal,
|
|
59
|
-
{ enabled: visible, modal: true }
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
if (!visible || !fieldKey) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const fieldConfig = fieldConfigs.find((f) => f.key === fieldKey);
|
|
67
|
-
if (!fieldConfig) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const isEnum = fieldConfig.type === "enum" && fieldConfig.options;
|
|
72
|
-
const isBoolean = fieldConfig.type === "boolean";
|
|
73
|
-
const isNumber = fieldConfig.type === "number";
|
|
74
|
-
|
|
75
|
-
const handleInputSubmit = (value: string) => {
|
|
76
|
-
if (isNumber) {
|
|
77
|
-
onSubmit(parseInt(value.replace(/[^0-9-]/g, ""), 10) || 0);
|
|
78
|
-
} else {
|
|
79
|
-
onSubmit(value);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const handleSelectIndexChange = (index: number, _option: SelectOption | null) => {
|
|
84
|
-
setSelectIndex(index);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const handleSelectSubmit = (_index: number, option: SelectOption | null) => {
|
|
88
|
-
if (option) {
|
|
89
|
-
onSubmit(option.value);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const handleBooleanSubmit = (_index: number, option: SelectOption | null) => {
|
|
94
|
-
if (option) {
|
|
95
|
-
onSubmit(option.value === true);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Boolean uses select with True/False options
|
|
100
|
-
const booleanOptions: SelectOption[] = [
|
|
101
|
-
{ name: "False", description: "", value: false },
|
|
102
|
-
{ name: "True", description: "", value: true },
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<box
|
|
107
|
-
position="absolute"
|
|
108
|
-
top={4}
|
|
109
|
-
left={6}
|
|
110
|
-
width="60%"
|
|
111
|
-
height={12}
|
|
112
|
-
backgroundColor={Theme.overlay}
|
|
113
|
-
border={true}
|
|
114
|
-
borderStyle="rounded"
|
|
115
|
-
borderColor={Theme.overlayTitle}
|
|
116
|
-
padding={1}
|
|
117
|
-
flexDirection="column"
|
|
118
|
-
gap={1}
|
|
119
|
-
zIndex={20}
|
|
120
|
-
>
|
|
121
|
-
<text fg={Theme.overlayTitle}>
|
|
122
|
-
<strong>Edit: {fieldConfig.label}</strong>
|
|
123
|
-
</text>
|
|
124
|
-
|
|
125
|
-
{isEnum && fieldConfig.options && (
|
|
126
|
-
<select
|
|
127
|
-
options={fieldConfig.options.map((o) => ({
|
|
128
|
-
name: o.name,
|
|
129
|
-
value: o.value,
|
|
130
|
-
description: "",
|
|
131
|
-
}))}
|
|
132
|
-
selectedIndex={selectIndex}
|
|
133
|
-
focused={true}
|
|
134
|
-
onChange={handleSelectIndexChange}
|
|
135
|
-
onSelect={handleSelectSubmit}
|
|
136
|
-
showScrollIndicator={true}
|
|
137
|
-
showDescription={false}
|
|
138
|
-
height={6}
|
|
139
|
-
width="100%"
|
|
140
|
-
wrapSelection={true}
|
|
141
|
-
selectedBackgroundColor="#61afef"
|
|
142
|
-
selectedTextColor="#1e2127"
|
|
143
|
-
/>
|
|
144
|
-
)}
|
|
145
|
-
|
|
146
|
-
{isBoolean && (
|
|
147
|
-
<select
|
|
148
|
-
options={booleanOptions}
|
|
149
|
-
selectedIndex={currentValue ? 1 : 0}
|
|
150
|
-
focused={true}
|
|
151
|
-
onSelect={handleBooleanSubmit}
|
|
152
|
-
showScrollIndicator={false}
|
|
153
|
-
showDescription={false}
|
|
154
|
-
height={2}
|
|
155
|
-
width="100%"
|
|
156
|
-
wrapSelection={true}
|
|
157
|
-
selectedBackgroundColor="#61afef"
|
|
158
|
-
selectedTextColor="#1e2127"
|
|
159
|
-
/>
|
|
160
|
-
)}
|
|
161
|
-
|
|
162
|
-
{!isEnum && !isBoolean && (
|
|
163
|
-
<input
|
|
164
|
-
value={inputValue}
|
|
165
|
-
placeholder={fieldConfig.placeholder ?? `Enter ${fieldConfig.label.toLowerCase()}...`}
|
|
166
|
-
focused={true}
|
|
167
|
-
onInput={(value) => setInputValue(value)}
|
|
168
|
-
onSubmit={handleInputSubmit}
|
|
169
|
-
/>
|
|
170
|
-
)}
|
|
171
|
-
|
|
172
|
-
<text fg={Theme.statusText}>
|
|
173
|
-
Enter to save, Esc to cancel
|
|
174
|
-
</text>
|
|
175
|
-
</box>
|
|
176
|
-
);
|
|
177
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { Theme } from "../theme.ts";
|
|
2
|
-
import { LogLevel, type LogEntry } from "../hooks/useLogStream.ts";
|
|
3
|
-
|
|
4
|
-
// Colors for different log levels
|
|
5
|
-
const LogColors: Record<LogLevel, string> = {
|
|
6
|
-
[LogLevel.Silly]: "#8c8c8c",
|
|
7
|
-
[LogLevel.Trace]: "#6dd6ff",
|
|
8
|
-
[LogLevel.Debug]: "#7bdcb5",
|
|
9
|
-
[LogLevel.Info]: "#d6dde6",
|
|
10
|
-
[LogLevel.Warn]: "#f5c542",
|
|
11
|
-
[LogLevel.Error]: "#f78888",
|
|
12
|
-
[LogLevel.Fatal]: "#ff5c8d",
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
interface LogsPanelProps {
|
|
16
|
-
/** Log entries to display */
|
|
17
|
-
logs: LogEntry[];
|
|
18
|
-
/** Whether the panel is visible */
|
|
19
|
-
visible: boolean;
|
|
20
|
-
/** Whether the panel is focused */
|
|
21
|
-
focused: boolean;
|
|
22
|
-
/** Whether to expand to fill available space */
|
|
23
|
-
expanded?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Panel displaying log entries with color-coded levels.
|
|
28
|
-
*/
|
|
29
|
-
export function LogsPanel({
|
|
30
|
-
logs,
|
|
31
|
-
visible,
|
|
32
|
-
focused,
|
|
33
|
-
expanded = false,
|
|
34
|
-
}: LogsPanelProps) {
|
|
35
|
-
if (!visible) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const borderColor = focused ? Theme.borderFocused : Theme.border;
|
|
40
|
-
const title = `Logs - ${logs.length}`;
|
|
41
|
-
|
|
42
|
-
// When expanded, grow to fill. Otherwise fixed height.
|
|
43
|
-
const boxProps = expanded
|
|
44
|
-
? { flexGrow: 1 }
|
|
45
|
-
: { height: 10, flexShrink: 0 };
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<box
|
|
49
|
-
flexDirection="column"
|
|
50
|
-
border={true}
|
|
51
|
-
borderStyle="rounded"
|
|
52
|
-
borderColor={borderColor}
|
|
53
|
-
title={title}
|
|
54
|
-
padding={1}
|
|
55
|
-
{...boxProps}
|
|
56
|
-
>
|
|
57
|
-
<scrollbox
|
|
58
|
-
scrollY={true}
|
|
59
|
-
flexGrow={1}
|
|
60
|
-
stickyScroll={true}
|
|
61
|
-
stickyStart="bottom"
|
|
62
|
-
focused={focused}
|
|
63
|
-
>
|
|
64
|
-
<box flexDirection="column" gap={0}>
|
|
65
|
-
{logs.map((log, idx) => {
|
|
66
|
-
const color = LogColors[log.level] ?? Theme.statusText;
|
|
67
|
-
// Strip ANSI codes but preserve line breaks
|
|
68
|
-
const sanitized = typeof Bun !== "undefined"
|
|
69
|
-
? Bun.stripANSI(log.message).trim()
|
|
70
|
-
: log.message.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").trim();
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<text key={`${log.timestamp.getTime()}-${idx}`} fg={color}>
|
|
74
|
-
{sanitized}
|
|
75
|
-
</text>
|
|
76
|
-
);
|
|
77
|
-
})}
|
|
78
|
-
|
|
79
|
-
{logs.length === 0 && (
|
|
80
|
-
<text fg={Theme.label}>No logs yet...</text>
|
|
81
|
-
)}
|
|
82
|
-
</box>
|
|
83
|
-
</scrollbox>
|
|
84
|
-
</box>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export { FieldRow } from "./FieldRow.tsx";
|
|
2
|
-
export { ActionButton } from "./ActionButton.tsx";
|
|
3
|
-
export { Header } from "./Header.tsx";
|
|
4
|
-
export { StatusBar } from "./StatusBar.tsx";
|
|
5
|
-
export { LogsPanel } from "./LogsPanel.tsx";
|
|
6
|
-
export { ResultsPanel } from "./ResultsPanel.tsx";
|
|
7
|
-
export { ConfigForm } from "./ConfigForm.tsx";
|
|
8
|
-
export { EditorModal } from "./EditorModal.tsx";
|
|
9
|
-
export { CliModal } from "./CliModal.tsx";
|
|
10
|
-
export { CommandSelector } from "./CommandSelector.tsx";
|
|
11
|
-
export { JsonHighlight, type JsonHighlightProps } from "./JsonHighlight.tsx";
|
|
12
|
-
|
|
13
|
-
export type { FieldType, FieldOption, FieldConfig } from "./types.ts";
|
package/src/tui/context/index.ts
DELETED
package/src/tui/hooks/index.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
useKeyboardHandler,
|
|
3
|
-
KeyboardPriority,
|
|
4
|
-
type KeyboardEvent,
|
|
5
|
-
} from "./useKeyboardHandler.ts";
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
useClipboard,
|
|
9
|
-
type UseClipboardResult,
|
|
10
|
-
} from "./useClipboard.ts";
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
useSpinner,
|
|
14
|
-
type UseSpinnerResult,
|
|
15
|
-
} from "./useSpinner.ts";
|
|
16
|
-
|
|
17
|
-
export {
|
|
18
|
-
useConfigState,
|
|
19
|
-
type UseConfigStateOptions,
|
|
20
|
-
type UseConfigStateResult,
|
|
21
|
-
} from "./useConfigState.ts";
|
|
22
|
-
|
|
23
|
-
export {
|
|
24
|
-
useCommandExecutor,
|
|
25
|
-
type UseCommandExecutorResult,
|
|
26
|
-
} from "./useCommandExecutor.ts";
|
|
27
|
-
|
|
28
|
-
export {
|
|
29
|
-
useLogStream,
|
|
30
|
-
LogLevel,
|
|
31
|
-
type LogEntry,
|
|
32
|
-
type LogEvent,
|
|
33
|
-
type LogSource,
|
|
34
|
-
type UseLogStreamResult,
|
|
35
|
-
} from "./useLogStream.ts";
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { useEffect, useId, useRef } from "react";
|
|
2
|
-
import {
|
|
3
|
-
useKeyboardContext,
|
|
4
|
-
KeyboardPriority,
|
|
5
|
-
type KeyboardHandler,
|
|
6
|
-
type KeyboardEvent,
|
|
7
|
-
} from "../context/KeyboardContext.tsx";
|
|
8
|
-
|
|
9
|
-
interface UseKeyboardHandlerOptions {
|
|
10
|
-
/**
|
|
11
|
-
* Whether the handler is currently enabled.
|
|
12
|
-
* When false, the handler is unregistered.
|
|
13
|
-
* Useful for conditionally handling keys only when focused.
|
|
14
|
-
* @default true
|
|
15
|
-
*/
|
|
16
|
-
enabled?: boolean;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* When true, automatically calls stopPropagation() after the handler runs,
|
|
20
|
-
* blocking keys from reaching lower-priority handlers.
|
|
21
|
-
* Does NOT block OpenTUI primitives (input/select) from receiving keys.
|
|
22
|
-
* Use this for modal dialogs that should capture keyboard focus.
|
|
23
|
-
* @default false
|
|
24
|
-
*/
|
|
25
|
-
modal?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Register a keyboard handler with the KeyboardProvider.
|
|
30
|
-
*
|
|
31
|
-
* @param handler - Callback invoked on keyboard events.
|
|
32
|
-
* - Call `event.stopPropagation()` to stop our handlers from receiving the event.
|
|
33
|
-
* - Call `event.key.preventDefault()` to also block OpenTUI primitives.
|
|
34
|
-
* @param priority - Handler priority level. Higher priorities are called first.
|
|
35
|
-
* @param options - Optional configuration (e.g., enabled flag, modal behavior).
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```tsx
|
|
39
|
-
* // Modal handler - blocks lower-priority handlers but lets OpenTUI primitives work
|
|
40
|
-
* useKeyboardHandler(
|
|
41
|
-
* (event) => {
|
|
42
|
-
* if (event.key.name === "escape") {
|
|
43
|
-
* onClose();
|
|
44
|
-
* }
|
|
45
|
-
* // Other keys pass through to <input>/<select> but not to ConfigForm
|
|
46
|
-
* },
|
|
47
|
-
* KeyboardPriority.Modal,
|
|
48
|
-
* { enabled: isVisible, modal: true }
|
|
49
|
-
* );
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
export function useKeyboardHandler(
|
|
53
|
-
handler: KeyboardHandler,
|
|
54
|
-
priority: KeyboardPriority,
|
|
55
|
-
options: UseKeyboardHandlerOptions = {}
|
|
56
|
-
): void {
|
|
57
|
-
const { enabled = true, modal = false } = options;
|
|
58
|
-
const { register, unregister } = useKeyboardContext();
|
|
59
|
-
const id = useId();
|
|
60
|
-
|
|
61
|
-
// Keep handler ref stable to avoid re-registrations on every render
|
|
62
|
-
const handlerRef = useRef(handler);
|
|
63
|
-
handlerRef.current = handler;
|
|
64
|
-
|
|
65
|
-
// Keep modal ref stable
|
|
66
|
-
const modalRef = useRef(modal);
|
|
67
|
-
modalRef.current = modal;
|
|
68
|
-
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (!enabled) {
|
|
71
|
-
unregister(id);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Register with a stable wrapper that calls the current handler
|
|
76
|
-
register(id, (event: KeyboardEvent) => {
|
|
77
|
-
handlerRef.current(event);
|
|
78
|
-
// For modals, always stop propagation to our handlers (but not OpenTUI primitives)
|
|
79
|
-
if (modalRef.current) {
|
|
80
|
-
event.stopPropagation();
|
|
81
|
-
}
|
|
82
|
-
}, priority);
|
|
83
|
-
|
|
84
|
-
return () => {
|
|
85
|
-
unregister(id);
|
|
86
|
-
};
|
|
87
|
-
}, [id, priority, enabled, register, unregister]);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export { KeyboardPriority };
|
|
91
|
-
export type { KeyboardEvent };
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from "react";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Log levels for display styling.
|
|
5
|
-
*/
|
|
6
|
-
export enum LogLevel {
|
|
7
|
-
Silly = "silly",
|
|
8
|
-
Trace = "trace",
|
|
9
|
-
Debug = "debug",
|
|
10
|
-
Info = "info",
|
|
11
|
-
Warn = "warn",
|
|
12
|
-
Error = "error",
|
|
13
|
-
Fatal = "fatal",
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Log entry for display.
|
|
18
|
-
*/
|
|
19
|
-
export interface LogEntry {
|
|
20
|
-
timestamp: Date;
|
|
21
|
-
level: LogLevel;
|
|
22
|
-
message: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Log event emitted by the log source.
|
|
27
|
-
*/
|
|
28
|
-
export interface LogEvent {
|
|
29
|
-
level: LogLevel;
|
|
30
|
-
message: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Log source that can be subscribed to.
|
|
35
|
-
*/
|
|
36
|
-
export interface LogSource {
|
|
37
|
-
/** Subscribe to log events, returns unsubscribe function */
|
|
38
|
-
subscribe: (callback: (event: LogEvent) => void) => () => void;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface UseLogStreamResult {
|
|
42
|
-
/** All collected log entries */
|
|
43
|
-
logs: LogEntry[];
|
|
44
|
-
/** Clear all logs */
|
|
45
|
-
clearLogs: () => void;
|
|
46
|
-
/** Add a log entry manually */
|
|
47
|
-
addLog: (level: LogLevel, message: string) => void;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Hook for subscribing to a log stream.
|
|
52
|
-
*
|
|
53
|
-
* @param source - Optional log source to subscribe to
|
|
54
|
-
* @returns Log stream state and functions
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```tsx
|
|
58
|
-
* const { logs, clearLogs } = useLogStream(myLogSource);
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
export function useLogStream(source?: LogSource): UseLogStreamResult {
|
|
62
|
-
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
63
|
-
|
|
64
|
-
// Subscribe to log source
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
if (!source) return;
|
|
67
|
-
|
|
68
|
-
const unsubscribe = source.subscribe((event: LogEvent) => {
|
|
69
|
-
setLogs((prev) => {
|
|
70
|
-
const newEntry: LogEntry = {
|
|
71
|
-
timestamp: new Date(),
|
|
72
|
-
level: event.level,
|
|
73
|
-
message: event.message,
|
|
74
|
-
};
|
|
75
|
-
return [...prev, newEntry];
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return () => {
|
|
80
|
-
unsubscribe?.();
|
|
81
|
-
};
|
|
82
|
-
}, [source]);
|
|
83
|
-
|
|
84
|
-
const clearLogs = useCallback(() => {
|
|
85
|
-
setLogs([]);
|
|
86
|
-
}, []);
|
|
87
|
-
|
|
88
|
-
const addLog = useCallback((level: LogLevel, message: string) => {
|
|
89
|
-
setLogs((prev) => [
|
|
90
|
-
...prev,
|
|
91
|
-
{ timestamp: new Date(), level, message },
|
|
92
|
-
]);
|
|
93
|
-
}, []);
|
|
94
|
-
|
|
95
|
-
return { logs, clearLogs, addLog };
|
|
96
|
-
}
|
package/src/tui/index.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// Main TUI components
|
|
2
|
-
export { TuiApp } from "./TuiApp.tsx";
|
|
3
|
-
export { TuiApplication, type TuiApplicationConfig, type CustomField } from "./TuiApplication.tsx";
|
|
4
|
-
|
|
5
|
-
// Theme
|
|
6
|
-
export { Theme, type ThemeColors } from "./theme.ts";
|
|
7
|
-
|
|
8
|
-
// Context
|
|
9
|
-
export {
|
|
10
|
-
KeyboardProvider,
|
|
11
|
-
useKeyboardContext,
|
|
12
|
-
KeyboardPriority,
|
|
13
|
-
type KeyboardEvent,
|
|
14
|
-
type KeyboardHandler,
|
|
15
|
-
} from "./context/index.ts";
|
|
16
|
-
|
|
17
|
-
// Hooks
|
|
18
|
-
export {
|
|
19
|
-
useKeyboardHandler,
|
|
20
|
-
useClipboard,
|
|
21
|
-
useSpinner,
|
|
22
|
-
useConfigState,
|
|
23
|
-
useCommandExecutor,
|
|
24
|
-
useLogStream,
|
|
25
|
-
LogLevel,
|
|
26
|
-
type UseClipboardResult,
|
|
27
|
-
type UseSpinnerResult,
|
|
28
|
-
type UseConfigStateOptions,
|
|
29
|
-
type UseConfigStateResult,
|
|
30
|
-
type UseCommandExecutorResult,
|
|
31
|
-
type LogEntry,
|
|
32
|
-
type LogEvent,
|
|
33
|
-
type LogSource,
|
|
34
|
-
type UseLogStreamResult,
|
|
35
|
-
} from "./hooks/index.ts";
|
|
36
|
-
|
|
37
|
-
// Components
|
|
38
|
-
export {
|
|
39
|
-
FieldRow,
|
|
40
|
-
ActionButton,
|
|
41
|
-
Header,
|
|
42
|
-
StatusBar,
|
|
43
|
-
LogsPanel,
|
|
44
|
-
ResultsPanel,
|
|
45
|
-
ConfigForm,
|
|
46
|
-
EditorModal,
|
|
47
|
-
CliModal,
|
|
48
|
-
CommandSelector,
|
|
49
|
-
JsonHighlight,
|
|
50
|
-
type FieldType,
|
|
51
|
-
type FieldOption,
|
|
52
|
-
type FieldConfig,
|
|
53
|
-
type JsonHighlightProps,
|
|
54
|
-
} from "./components/index.ts";
|
|
55
|
-
|
|
56
|
-
// Utilities
|
|
57
|
-
export {
|
|
58
|
-
schemaToFieldConfigs,
|
|
59
|
-
groupFieldConfigs,
|
|
60
|
-
getFieldDisplayValue,
|
|
61
|
-
buildCliCommand,
|
|
62
|
-
} from "./utils/index.ts";
|
|
63
|
-
|
|
64
|
-
// Legacy export for backward compatibility
|
|
65
|
-
export * from "./app.ts";
|
package/src/tui/utils/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
schemaToFieldConfigs,
|
|
3
|
-
groupFieldConfigs,
|
|
4
|
-
getFieldDisplayValue,
|
|
5
|
-
} from "./schemaToFields.ts";
|
|
6
|
-
|
|
7
|
-
export { buildCliCommand } from "./buildCliCommand.ts";
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
loadPersistedParameters,
|
|
11
|
-
savePersistedParameters,
|
|
12
|
-
clearPersistedParameters,
|
|
13
|
-
} from "./parameterPersistence.ts";
|
package/src/types/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./command.ts";
|