claudeup 4.6.0 → 4.7.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.
- package/package.json +1 -1
- package/src/data/settings-catalog.js +2 -7
- package/src/data/settings-catalog.ts +2 -7
- package/src/opentui.d.ts +7 -2
- package/src/services/claude-settings.js +31 -4
- package/src/services/claude-settings.ts +31 -4
- package/src/services/settings-manager.js +84 -5
- package/src/services/settings-manager.ts +86 -5
- package/src/types/index.ts +1 -1
- package/src/ui/adapters/settingsAdapter.js +8 -8
- package/src/ui/adapters/settingsAdapter.ts +8 -8
- package/src/ui/components/TabBar.js +1 -23
- package/src/ui/components/TabBar.tsx +1 -26
- package/src/ui/components/modals/ConfirmModal.js +1 -1
- package/src/ui/components/modals/ConfirmModal.tsx +17 -16
- package/src/ui/components/modals/InputModal.js +2 -13
- package/src/ui/components/modals/InputModal.tsx +21 -24
- package/src/ui/components/modals/LoadingModal.js +1 -1
- package/src/ui/components/modals/LoadingModal.tsx +6 -6
- package/src/ui/components/modals/MessageModal.js +4 -4
- package/src/ui/components/modals/MessageModal.tsx +13 -13
- package/src/ui/components/modals/ModalContainer.js +25 -2
- package/src/ui/components/modals/ModalContainer.tsx +25 -2
- package/src/ui/components/modals/SelectModal.js +3 -4
- package/src/ui/components/modals/SelectModal.tsx +18 -15
- package/src/ui/renderers/settingsRenderers.js +1 -1
- package/src/ui/renderers/settingsRenderers.tsx +5 -3
- package/src/ui/screens/CliToolsScreen.js +2 -2
- package/src/ui/screens/CliToolsScreen.tsx +3 -1
- package/src/ui/screens/EnvVarsScreen.js +27 -10
- package/src/ui/screens/EnvVarsScreen.tsx +33 -16
- package/src/ui/screens/McpRegistryScreen.js +2 -2
- package/src/ui/screens/McpRegistryScreen.tsx +3 -1
- package/src/ui/screens/McpScreen.js +1 -1
- package/src/ui/screens/McpScreen.tsx +2 -1
- package/src/ui/screens/ModelSelectorScreen.js +2 -2
- package/src/ui/screens/ModelSelectorScreen.tsx +3 -2
- package/src/ui/screens/ProfilesScreen.js +1 -1
- package/src/ui/screens/ProfilesScreen.tsx +2 -1
- package/src/ui/screens/StatusLineScreen.js +1 -1
- package/src/ui/screens/StatusLineScreen.tsx +2 -1
- package/src/ui/state/DimensionsContext.js +2 -2
- package/src/ui/state/DimensionsContext.tsx +3 -3
- package/src/ui/components/ScrollableDetail.js +0 -23
- package/src/ui/components/ScrollableDetail.tsx +0 -55
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { useKeyboard } from "../../hooks/useKeyboard.js";
|
|
1
|
+
import React from "react";
|
|
3
2
|
|
|
4
3
|
interface InputModalProps {
|
|
5
4
|
/** Modal title */
|
|
@@ -19,43 +18,41 @@ export function InputModal({
|
|
|
19
18
|
label,
|
|
20
19
|
defaultValue = "",
|
|
21
20
|
onSubmit,
|
|
22
|
-
onCancel,
|
|
23
21
|
}: InputModalProps) {
|
|
24
|
-
const [value, setValue] = useState(defaultValue);
|
|
25
|
-
|
|
26
|
-
useKeyboard((key) => {
|
|
27
|
-
if (key.name === "enter") {
|
|
28
|
-
onSubmit(value);
|
|
29
|
-
} else if (key.name === "escape") {
|
|
30
|
-
onCancel();
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
22
|
return (
|
|
35
23
|
<box
|
|
36
24
|
flexDirection="column"
|
|
37
25
|
border
|
|
38
26
|
borderStyle="rounded"
|
|
39
|
-
borderColor="
|
|
40
|
-
backgroundColor="#
|
|
41
|
-
paddingLeft={
|
|
42
|
-
paddingRight={
|
|
27
|
+
borderColor="#525252"
|
|
28
|
+
backgroundColor="#1C1C1E"
|
|
29
|
+
paddingLeft={3}
|
|
30
|
+
paddingRight={3}
|
|
43
31
|
paddingTop={1}
|
|
44
32
|
paddingBottom={1}
|
|
45
33
|
width={60}
|
|
46
34
|
>
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
35
|
+
<box marginBottom={1}>
|
|
36
|
+
<text fg="#EDEDED">
|
|
37
|
+
<strong>{title}</strong>
|
|
38
|
+
</text>
|
|
39
|
+
</box>
|
|
50
40
|
|
|
51
|
-
<box
|
|
52
|
-
<text>{label}</text>
|
|
41
|
+
<box marginBottom={1}>
|
|
42
|
+
<text fg="#A1A1AA">{label}</text>
|
|
53
43
|
</box>
|
|
54
44
|
|
|
55
|
-
<
|
|
45
|
+
<box border borderStyle="rounded" borderColor="#3F3F46" paddingLeft={1} paddingRight={1} width={54}>
|
|
46
|
+
<input
|
|
47
|
+
value={defaultValue}
|
|
48
|
+
onSubmit={onSubmit as any}
|
|
49
|
+
focused
|
|
50
|
+
width={50}
|
|
51
|
+
/>
|
|
52
|
+
</box>
|
|
56
53
|
|
|
57
54
|
<box marginTop={1}>
|
|
58
|
-
<text fg="#
|
|
55
|
+
<text fg="#71717A">↵ to confirm • Esc to cancel</text>
|
|
59
56
|
</box>
|
|
60
57
|
</box>
|
|
61
58
|
);
|
|
@@ -9,6 +9,6 @@ export function LoadingModal({ message }) {
|
|
|
9
9
|
}, 80);
|
|
10
10
|
return () => clearInterval(interval);
|
|
11
11
|
}, []);
|
|
12
|
-
return (_jsxs("box", { flexDirection: "row", border: true, borderStyle: "rounded", borderColor: "
|
|
12
|
+
return (_jsxs("box", { flexDirection: "row", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, children: [_jsx("text", { fg: "#A1A1AA", children: SPINNER_FRAMES[frame] }), _jsxs("text", { fg: "#EDEDED", children: [" ", message] })] }));
|
|
13
13
|
}
|
|
14
14
|
export default LoadingModal;
|
|
@@ -23,15 +23,15 @@ export function LoadingModal({ message }: LoadingModalProps) {
|
|
|
23
23
|
flexDirection="row"
|
|
24
24
|
border
|
|
25
25
|
borderStyle="rounded"
|
|
26
|
-
borderColor="
|
|
27
|
-
backgroundColor="#
|
|
28
|
-
paddingLeft={
|
|
29
|
-
paddingRight={
|
|
26
|
+
borderColor="#525252"
|
|
27
|
+
backgroundColor="#1C1C1E"
|
|
28
|
+
paddingLeft={3}
|
|
29
|
+
paddingRight={3}
|
|
30
30
|
paddingTop={1}
|
|
31
31
|
paddingBottom={1}
|
|
32
32
|
>
|
|
33
|
-
<text fg="
|
|
34
|
-
<text> {message}</text>
|
|
33
|
+
<text fg="#A1A1AA">{SPINNER_FRAMES[frame]}</text>
|
|
34
|
+
<text fg="#EDEDED"> {message}</text>
|
|
35
35
|
</box>
|
|
36
36
|
);
|
|
37
37
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
2
|
import { useKeyboard } from "../../hooks/useKeyboard.js";
|
|
3
3
|
const variantConfig = {
|
|
4
|
-
info: { icon: "ℹ", color: "
|
|
5
|
-
success: { icon: "✓", color: "
|
|
6
|
-
error: { icon: "✗", color: "
|
|
4
|
+
info: { icon: "ℹ", color: "#60A5FA" },
|
|
5
|
+
success: { icon: "✓", color: "#4ADE80" },
|
|
6
|
+
error: { icon: "✗", color: "#F87171" },
|
|
7
7
|
};
|
|
8
8
|
export function MessageModal({ title, message, variant, onDismiss, }) {
|
|
9
9
|
const config = variantConfig[variant];
|
|
@@ -11,6 +11,6 @@ export function MessageModal({ title, message, variant, onDismiss, }) {
|
|
|
11
11
|
// Any key dismisses
|
|
12
12
|
onDismiss();
|
|
13
13
|
});
|
|
14
|
-
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor:
|
|
14
|
+
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsxs("box", { marginBottom: 1, children: [_jsxs("text", { fg: config.color, children: [config.icon, " "] }), _jsx("text", { fg: "#EDEDED", children: _jsx("strong", { children: title }) })] }), _jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#A1A1AA", children: message }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#71717A", children: "Press any key to continue" }) })] }));
|
|
15
15
|
}
|
|
16
16
|
export default MessageModal;
|
|
@@ -13,9 +13,9 @@ interface MessageModalProps {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const variantConfig = {
|
|
16
|
-
info: { icon: "ℹ", color: "
|
|
17
|
-
success: { icon: "✓", color: "
|
|
18
|
-
error: { icon: "✗", color: "
|
|
16
|
+
info: { icon: "ℹ", color: "#60A5FA" },
|
|
17
|
+
success: { icon: "✓", color: "#4ADE80" },
|
|
18
|
+
error: { icon: "✗", color: "#F87171" },
|
|
19
19
|
} as const;
|
|
20
20
|
|
|
21
21
|
export function MessageModal({
|
|
@@ -36,27 +36,27 @@ export function MessageModal({
|
|
|
36
36
|
flexDirection="column"
|
|
37
37
|
border
|
|
38
38
|
borderStyle="rounded"
|
|
39
|
-
borderColor=
|
|
40
|
-
backgroundColor="#
|
|
41
|
-
paddingLeft={
|
|
42
|
-
paddingRight={
|
|
39
|
+
borderColor="#525252"
|
|
40
|
+
backgroundColor="#1C1C1E"
|
|
41
|
+
paddingLeft={3}
|
|
42
|
+
paddingRight={3}
|
|
43
43
|
paddingTop={1}
|
|
44
44
|
paddingBottom={1}
|
|
45
45
|
width={60}
|
|
46
46
|
>
|
|
47
|
-
<box>
|
|
47
|
+
<box marginBottom={1}>
|
|
48
48
|
<text fg={config.color}>{config.icon} </text>
|
|
49
|
-
<text>
|
|
49
|
+
<text fg="#EDEDED">
|
|
50
50
|
<strong>{title}</strong>
|
|
51
51
|
</text>
|
|
52
52
|
</box>
|
|
53
53
|
|
|
54
|
-
<box
|
|
55
|
-
<text>{message}</text>
|
|
54
|
+
<box marginBottom={1}>
|
|
55
|
+
<text fg="#A1A1AA">{message}</text>
|
|
56
56
|
</box>
|
|
57
57
|
|
|
58
|
-
<box>
|
|
59
|
-
<text fg="#
|
|
58
|
+
<box marginTop={1}>
|
|
59
|
+
<text fg="#71717A">Press any key to continue</text>
|
|
60
60
|
</box>
|
|
61
61
|
</box>
|
|
62
62
|
);
|
|
@@ -11,6 +11,9 @@ import { LoadingModal } from "./LoadingModal.js";
|
|
|
11
11
|
* Container that renders the active modal as an overlay
|
|
12
12
|
* Handles ALL keyboard events when a modal is open to avoid
|
|
13
13
|
* conflicts with multiple useKeyboard hooks in child components
|
|
14
|
+
*
|
|
15
|
+
* Input modal: Enter/Escape handled by OpenTUI <input> onSubmit + ModalContainer escape
|
|
16
|
+
* Select modal: keyboard fully handled here (index tracking + Enter/arrows)
|
|
14
17
|
*/
|
|
15
18
|
export function ModalContainer() {
|
|
16
19
|
const { state } = useApp();
|
|
@@ -25,14 +28,14 @@ export function ModalContainer() {
|
|
|
25
28
|
setSelectIndex(modal.defaultIndex ?? 0);
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
|
-
// Handle
|
|
31
|
+
// Handle keyboard events for modals
|
|
29
32
|
useKeyboard((key) => {
|
|
30
33
|
if (!modal)
|
|
31
34
|
return;
|
|
32
35
|
if (modal.type === "loading")
|
|
33
36
|
return;
|
|
34
37
|
// Escape — close any modal
|
|
35
|
-
if (key.name === "escape"
|
|
38
|
+
if (key.name === "escape") {
|
|
36
39
|
if (modal.type === "confirm")
|
|
37
40
|
modal.onCancel();
|
|
38
41
|
else if (modal.type === "input")
|
|
@@ -43,6 +46,20 @@ export function ModalContainer() {
|
|
|
43
46
|
modal.onDismiss();
|
|
44
47
|
return;
|
|
45
48
|
}
|
|
49
|
+
// 'q' to close — but NOT for input modals (need to type 'q')
|
|
50
|
+
if (key.name === "q" && modal.type !== "input") {
|
|
51
|
+
if (modal.type === "confirm")
|
|
52
|
+
modal.onCancel();
|
|
53
|
+
else if (modal.type === "select")
|
|
54
|
+
modal.onCancel();
|
|
55
|
+
else if (modal.type === "message")
|
|
56
|
+
modal.onDismiss();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Input modal — let OpenTUI <input> handle Enter via onSubmit
|
|
60
|
+
if (modal.type === "input") {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
46
63
|
// Select modal — handle navigation and selection
|
|
47
64
|
if (modal.type === "select") {
|
|
48
65
|
if (key.name === "return" || key.name === "enter") {
|
|
@@ -62,6 +79,12 @@ export function ModalContainer() {
|
|
|
62
79
|
modal.onDismiss();
|
|
63
80
|
}
|
|
64
81
|
}
|
|
82
|
+
// Confirm modal — Enter to confirm
|
|
83
|
+
if (modal.type === "confirm") {
|
|
84
|
+
if (key.name === "return" || key.name === "enter") {
|
|
85
|
+
modal.onConfirm();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
65
88
|
});
|
|
66
89
|
if (!modal) {
|
|
67
90
|
return null;
|
|
@@ -11,6 +11,9 @@ import { LoadingModal } from "./LoadingModal.js";
|
|
|
11
11
|
* Container that renders the active modal as an overlay
|
|
12
12
|
* Handles ALL keyboard events when a modal is open to avoid
|
|
13
13
|
* conflicts with multiple useKeyboard hooks in child components
|
|
14
|
+
*
|
|
15
|
+
* Input modal: Enter/Escape handled by OpenTUI <input> onSubmit + ModalContainer escape
|
|
16
|
+
* Select modal: keyboard fully handled here (index tracking + Enter/arrows)
|
|
14
17
|
*/
|
|
15
18
|
export function ModalContainer() {
|
|
16
19
|
const { state } = useApp();
|
|
@@ -28,13 +31,13 @@ export function ModalContainer() {
|
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
// Handle
|
|
34
|
+
// Handle keyboard events for modals
|
|
32
35
|
useKeyboard((key) => {
|
|
33
36
|
if (!modal) return;
|
|
34
37
|
if (modal.type === "loading") return;
|
|
35
38
|
|
|
36
39
|
// Escape — close any modal
|
|
37
|
-
if (key.name === "escape"
|
|
40
|
+
if (key.name === "escape") {
|
|
38
41
|
if (modal.type === "confirm") modal.onCancel();
|
|
39
42
|
else if (modal.type === "input") modal.onCancel();
|
|
40
43
|
else if (modal.type === "select") modal.onCancel();
|
|
@@ -42,6 +45,19 @@ export function ModalContainer() {
|
|
|
42
45
|
return;
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
// 'q' to close — but NOT for input modals (need to type 'q')
|
|
49
|
+
if (key.name === "q" && modal.type !== "input") {
|
|
50
|
+
if (modal.type === "confirm") modal.onCancel();
|
|
51
|
+
else if (modal.type === "select") modal.onCancel();
|
|
52
|
+
else if (modal.type === "message") modal.onDismiss();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Input modal — let OpenTUI <input> handle Enter via onSubmit
|
|
57
|
+
if (modal.type === "input") {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
// Select modal — handle navigation and selection
|
|
46
62
|
if (modal.type === "select") {
|
|
47
63
|
if (key.name === "return" || key.name === "enter") {
|
|
@@ -60,6 +76,13 @@ export function ModalContainer() {
|
|
|
60
76
|
modal.onDismiss();
|
|
61
77
|
}
|
|
62
78
|
}
|
|
79
|
+
|
|
80
|
+
// Confirm modal — Enter to confirm
|
|
81
|
+
if (modal.type === "confirm") {
|
|
82
|
+
if (key.name === "return" || key.name === "enter") {
|
|
83
|
+
modal.onConfirm();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
63
86
|
});
|
|
64
87
|
|
|
65
88
|
if (!modal) {
|
|
@@ -3,10 +3,9 @@ export function SelectModal({ title, message, options, defaultIndex, onSelect: _
|
|
|
3
3
|
// Keyboard handling is done by ModalContainer
|
|
4
4
|
// defaultIndex is the live selectedIndex from ModalContainer state
|
|
5
5
|
const selectedIndex = defaultIndex ?? 0;
|
|
6
|
-
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "
|
|
6
|
+
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 50, children: [_jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#EDEDED", children: _jsx("strong", { children: title }) }) }), _jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#A1A1AA", children: message }) }), _jsx("box", { flexDirection: "column", paddingLeft: 1, children: options.map((option, idx) => {
|
|
7
7
|
const isSelected = idx === selectedIndex;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#666666", children: "\u2191\u2193 Select \u2022 Enter \u2022 Esc" }) })] }));
|
|
8
|
+
return (_jsxs("text", { fg: isSelected ? "#F4F4F5" : "#A1A1AA", children: [_jsx("span", { fg: isSelected ? "#F4F4F5" : "#71717A", children: isSelected ? "❯ " : " " }), isSelected ? _jsx("strong", { children: option.label }) : option.label] }, option.value));
|
|
9
|
+
}) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#71717A", children: "\u2191\u2193 Select \u2022 \u21B5 Confirm \u2022 Esc Cancel" }) })] }));
|
|
11
10
|
}
|
|
12
11
|
export default SelectModal;
|
|
@@ -33,37 +33,40 @@ export function SelectModal({
|
|
|
33
33
|
flexDirection="column"
|
|
34
34
|
border
|
|
35
35
|
borderStyle="rounded"
|
|
36
|
-
borderColor="
|
|
37
|
-
backgroundColor="#
|
|
38
|
-
paddingLeft={
|
|
39
|
-
paddingRight={
|
|
36
|
+
borderColor="#525252"
|
|
37
|
+
backgroundColor="#1C1C1E"
|
|
38
|
+
paddingLeft={3}
|
|
39
|
+
paddingRight={3}
|
|
40
40
|
paddingTop={1}
|
|
41
41
|
paddingBottom={1}
|
|
42
42
|
width={50}
|
|
43
43
|
>
|
|
44
|
-
<
|
|
45
|
-
<
|
|
46
|
-
|
|
44
|
+
<box marginBottom={1}>
|
|
45
|
+
<text fg="#EDEDED">
|
|
46
|
+
<strong>{title}</strong>
|
|
47
|
+
</text>
|
|
48
|
+
</box>
|
|
47
49
|
|
|
48
|
-
<box
|
|
49
|
-
<text>{message}</text>
|
|
50
|
+
<box marginBottom={1}>
|
|
51
|
+
<text fg="#A1A1AA">{message}</text>
|
|
50
52
|
</box>
|
|
51
53
|
|
|
52
|
-
<box flexDirection="column">
|
|
54
|
+
<box flexDirection="column" paddingLeft={1}>
|
|
53
55
|
{options.map((option, idx) => {
|
|
54
56
|
const isSelected = idx === selectedIndex;
|
|
55
|
-
const label = isSelected ? `> ${option.label}` : ` ${option.label}`;
|
|
56
57
|
return (
|
|
57
|
-
<text key={option.value} fg={isSelected ? "
|
|
58
|
-
{isSelected
|
|
59
|
-
|
|
58
|
+
<text key={option.value} fg={isSelected ? "#F4F4F5" : "#A1A1AA"}>
|
|
59
|
+
<span fg={isSelected ? "#F4F4F5" : "#71717A"}>
|
|
60
|
+
{isSelected ? "❯ " : " "}
|
|
61
|
+
</span>
|
|
62
|
+
{isSelected ? <strong>{option.label}</strong> : option.label}
|
|
60
63
|
</text>
|
|
61
64
|
);
|
|
62
65
|
})}
|
|
63
66
|
</box>
|
|
64
67
|
|
|
65
68
|
<box marginTop={1}>
|
|
66
|
-
<text fg="#
|
|
69
|
+
<text fg="#71717A">↑↓ Select • ↵ Confirm • Esc Cancel</text>
|
|
67
70
|
</box>
|
|
68
71
|
</box>
|
|
69
72
|
);
|
|
@@ -8,7 +8,7 @@ const categoryRenderer = {
|
|
|
8
8
|
const star = item.category === "recommended" ? "★ " : "";
|
|
9
9
|
const label = `${star}${CATEGORY_LABELS[item.category]}`;
|
|
10
10
|
const bg = isSelected ? theme.selection.bg : CATEGORY_BG[item.category];
|
|
11
|
-
return (_jsx("text", { bg: bg, fg: "white", children: _jsxs("strong", { children: [" ", label, " "] }) }));
|
|
11
|
+
return (_jsx("box", { width: "100%", children: _jsx("text", { bg: bg, fg: "white", children: _jsxs("strong", { children: [" ", label, " "] }) }) }));
|
|
12
12
|
},
|
|
13
13
|
renderDetail: ({ item }) => {
|
|
14
14
|
const cat = item.category;
|
|
@@ -24,9 +24,11 @@ const categoryRenderer: ItemRenderer<SettingsCategoryItem> = {
|
|
|
24
24
|
const bg = isSelected ? theme.selection.bg : CATEGORY_BG[item.category];
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
27
|
+
<box width="100%">
|
|
28
|
+
<text bg={bg} fg="white">
|
|
29
|
+
<strong> {label} </strong>
|
|
30
|
+
</text>
|
|
31
|
+
</box>
|
|
30
32
|
);
|
|
31
33
|
},
|
|
32
34
|
|
|
@@ -6,9 +6,9 @@ import { useApp, useModal } from "../state/AppContext.js";
|
|
|
6
6
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
7
7
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
8
8
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
9
|
-
import { ScrollableList } from "../components/ScrollableList.js";
|
|
10
9
|
import { cliTools } from "../../data/cli-tools.js";
|
|
11
10
|
import { renderCliToolRow, renderCliToolDetail, } from "../renderers/cliToolRenderers.js";
|
|
11
|
+
import { ScrollableList } from "../components/ScrollableList.js";
|
|
12
12
|
const execAsync = promisify(exec);
|
|
13
13
|
// ─── Version helpers ───────────────────────────────────────────────────────────
|
|
14
14
|
// Session-level cache
|
|
@@ -328,6 +328,6 @@ export function CliToolsScreen() {
|
|
|
328
328
|
const installedCount = toolStatuses.filter((s) => s.installed).length;
|
|
329
329
|
const updateCount = toolStatuses.filter((s) => s.hasUpdate).length;
|
|
330
330
|
const statusContent = (_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Installed: " }), _jsxs("span", { fg: "cyan", children: [installedCount, "/", toolStatuses.length] }), updateCount > 0 && (_jsxs(_Fragment, { children: [_jsx("span", { fg: "gray", children: " \u2502 Updates: " }), _jsx("span", { fg: "yellow", children: updateCount })] }))] }));
|
|
331
|
-
return (_jsx(ScreenLayout, { title: "claudeup CLI Tools", currentScreen: "cli-tools", statusLine: statusContent, footerHints: "Enter:install \u2502 a:update all \u2502 c:fix conflict \u2502 r:refresh", listPanel: _jsx(ScrollableList, { items: toolStatuses, selectedIndex: cliToolsState.selectedIndex, renderItem: renderCliToolRow, maxHeight: dimensions.listPanelHeight }), detailPanel: renderCliToolDetail(selectedStatus) }));
|
|
331
|
+
return (_jsx(ScreenLayout, { title: "claudeup CLI Tools", currentScreen: "cli-tools", statusLine: statusContent, footerHints: "Enter:install \u2502 a:update all \u2502 c:fix conflict \u2502 r:refresh", listPanel: _jsx(ScrollableList, { items: toolStatuses, selectedIndex: cliToolsState.selectedIndex, renderItem: renderCliToolRow, maxHeight: dimensions.listPanelHeight, getKey: (status) => status.tool.name }), detailPanel: renderCliToolDetail(selectedStatus) }));
|
|
332
332
|
}
|
|
333
333
|
export default CliToolsScreen;
|
|
@@ -5,7 +5,7 @@ import { useApp, useModal } from "../state/AppContext.js";
|
|
|
5
5
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
6
6
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
7
7
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
import { cliTools } from "../../data/cli-tools.js";
|
|
10
10
|
import {
|
|
11
11
|
renderCliToolRow,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type CliToolStatus,
|
|
14
14
|
type InstallMethod,
|
|
15
15
|
} from "../renderers/cliToolRenderers.js";
|
|
16
|
+
import { ScrollableList } from "../components/ScrollableList.js";
|
|
16
17
|
|
|
17
18
|
const execAsync = promisify(exec);
|
|
18
19
|
|
|
@@ -423,6 +424,7 @@ export function CliToolsScreen() {
|
|
|
423
424
|
selectedIndex={cliToolsState.selectedIndex}
|
|
424
425
|
renderItem={renderCliToolRow}
|
|
425
426
|
maxHeight={dimensions.listPanelHeight}
|
|
427
|
+
getKey={(status) => status.tool.name}
|
|
426
428
|
/>
|
|
427
429
|
}
|
|
428
430
|
detailPanel={renderCliToolDetail(selectedStatus)}
|
|
@@ -4,11 +4,11 @@ import { useApp, useModal } from "../state/AppContext.js";
|
|
|
4
4
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
5
5
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
6
6
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
7
|
-
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
7
|
import { SETTINGS_CATALOG, } from "../../data/settings-catalog.js";
|
|
9
|
-
import { readAllSettingsBothScopes, writeSettingValue, } from "../../services/settings-manager.js";
|
|
8
|
+
import { readAllSettingsBothScopes, writeSettingValue, discoverOutputStyles, } from "../../services/settings-manager.js";
|
|
10
9
|
import { buildSettingsBrowserItems, } from "../adapters/settingsAdapter.js";
|
|
11
10
|
import { renderSettingRow, renderSettingDetail } from "../renderers/settingsRenderers.js";
|
|
11
|
+
import { ScrollableList } from "../components/ScrollableList.js";
|
|
12
12
|
export function SettingsScreen() {
|
|
13
13
|
const { state, dispatch } = useApp();
|
|
14
14
|
const { settings } = state;
|
|
@@ -17,6 +17,11 @@ export function SettingsScreen() {
|
|
|
17
17
|
const fetchData = useCallback(async () => {
|
|
18
18
|
dispatch({ type: "SETTINGS_DATA_LOADING" });
|
|
19
19
|
try {
|
|
20
|
+
// Populate dynamic output style options from installed plugins
|
|
21
|
+
const outputStyleSetting = SETTINGS_CATALOG.find((s) => s.id === "output-style");
|
|
22
|
+
if (outputStyleSetting && outputStyleSetting.type === "select") {
|
|
23
|
+
outputStyleSetting.options = await discoverOutputStyles(state.projectPath);
|
|
24
|
+
}
|
|
20
25
|
const values = await readAllSettingsBothScopes(SETTINGS_CATALOG, state.projectPath);
|
|
21
26
|
dispatch({ type: "SETTINGS_DATA_SUCCESS", values });
|
|
22
27
|
}
|
|
@@ -76,15 +81,27 @@ export function SettingsScreen() {
|
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
else {
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
// String type: if already set, clear it; if unset, show input modal
|
|
85
|
+
if (currentValue !== undefined && currentValue !== "") {
|
|
86
|
+
try {
|
|
87
|
+
await writeSettingValue(setting, undefined, scope, state.projectPath);
|
|
88
|
+
await fetchData();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
92
|
+
}
|
|
85
93
|
}
|
|
86
|
-
|
|
87
|
-
await modal.
|
|
94
|
+
else {
|
|
95
|
+
const newValue = await modal.input(`${setting.name} — ${scope}`, setting.description, currentValue || setting.defaultValue || "");
|
|
96
|
+
if (newValue === null)
|
|
97
|
+
return;
|
|
98
|
+
try {
|
|
99
|
+
await writeSettingValue(setting, newValue || undefined, scope, state.projectPath);
|
|
100
|
+
await fetchData();
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
104
|
+
}
|
|
88
105
|
}
|
|
89
106
|
}
|
|
90
107
|
};
|
|
@@ -3,19 +3,20 @@ import { useApp, useModal } from "../state/AppContext.js";
|
|
|
3
3
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
4
4
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
5
5
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
6
|
-
import { ScrollableList } from "../components/ScrollableList.js";
|
|
7
6
|
import {
|
|
8
7
|
SETTINGS_CATALOG,
|
|
9
8
|
} from "../../data/settings-catalog.js";
|
|
10
9
|
import {
|
|
11
10
|
readAllSettingsBothScopes,
|
|
12
11
|
writeSettingValue,
|
|
12
|
+
discoverOutputStyles,
|
|
13
13
|
} from "../../services/settings-manager.js";
|
|
14
14
|
import {
|
|
15
15
|
buildSettingsBrowserItems,
|
|
16
16
|
type SettingsBrowserItem,
|
|
17
17
|
} from "../adapters/settingsAdapter.js";
|
|
18
18
|
import { renderSettingRow, renderSettingDetail } from "../renderers/settingsRenderers.js";
|
|
19
|
+
import { ScrollableList } from "../components/ScrollableList.js";
|
|
19
20
|
|
|
20
21
|
export function SettingsScreen() {
|
|
21
22
|
const { state, dispatch } = useApp();
|
|
@@ -26,6 +27,12 @@ export function SettingsScreen() {
|
|
|
26
27
|
const fetchData = useCallback(async () => {
|
|
27
28
|
dispatch({ type: "SETTINGS_DATA_LOADING" });
|
|
28
29
|
try {
|
|
30
|
+
// Populate dynamic output style options from installed plugins
|
|
31
|
+
const outputStyleSetting = SETTINGS_CATALOG.find((s) => s.id === "output-style");
|
|
32
|
+
if (outputStyleSetting && outputStyleSetting.type === "select") {
|
|
33
|
+
outputStyleSetting.options = await discoverOutputStyles(state.projectPath);
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
const values = await readAllSettingsBothScopes(
|
|
30
37
|
SETTINGS_CATALOG,
|
|
31
38
|
state.projectPath,
|
|
@@ -95,22 +102,32 @@ export function SettingsScreen() {
|
|
|
95
102
|
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
96
103
|
}
|
|
97
104
|
} else {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
scope
|
|
109
|
-
|
|
105
|
+
// String type: if already set, clear it; if unset, show input modal
|
|
106
|
+
if (currentValue !== undefined && currentValue !== "") {
|
|
107
|
+
try {
|
|
108
|
+
await writeSettingValue(setting, undefined, scope, state.projectPath);
|
|
109
|
+
await fetchData();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
const newValue = await modal.input(
|
|
115
|
+
`${setting.name} — ${scope}`,
|
|
116
|
+
setting.description,
|
|
117
|
+
currentValue || setting.defaultValue || "",
|
|
110
118
|
);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
if (newValue === null) return;
|
|
120
|
+
try {
|
|
121
|
+
await writeSettingValue(
|
|
122
|
+
setting,
|
|
123
|
+
newValue || undefined,
|
|
124
|
+
scope,
|
|
125
|
+
state.projectPath,
|
|
126
|
+
);
|
|
127
|
+
await fetchData();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
130
|
+
}
|
|
114
131
|
}
|
|
115
132
|
}
|
|
116
133
|
};
|
|
@@ -4,9 +4,9 @@ import { useApp, useModal, useNavigation } from "../state/AppContext.js";
|
|
|
4
4
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
5
5
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
6
6
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
7
|
-
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
7
|
import { searchMcpServers, formatDate } from "../../services/mcp-registry.js";
|
|
9
8
|
import { addMcpServer, setAllowMcp } from "../../services/claude-settings.js";
|
|
9
|
+
import { ScrollableList } from "../components/ScrollableList.js";
|
|
10
10
|
/**
|
|
11
11
|
* Deduplicate servers by name, keeping only the latest version.
|
|
12
12
|
* Uses version string comparison, falling back to published_at date.
|
|
@@ -222,6 +222,6 @@ export function McpRegistryScreen() {
|
|
|
222
222
|
isActive: isSearchActive,
|
|
223
223
|
query: searchQuery,
|
|
224
224
|
placeholder: searchPlaceholder,
|
|
225
|
-
}, footerHints: footerHints, listPanel: isLoading ? (_jsx("text", { fg: "gray", children: "Loading..." })) : error ? (_jsxs("text", { fg: "red", children: ["Error: ", error] })) : servers.length === 0 ? (_jsx("text", { fg: "gray", children: "No servers found" })) : (_jsx(ScrollableList, { items: servers, selectedIndex: mcpRegistry.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight })), detailPanel: renderDetail() }));
|
|
225
|
+
}, footerHints: footerHints, listPanel: isLoading ? (_jsx("text", { fg: "gray", children: "Loading..." })) : error ? (_jsxs("text", { fg: "red", children: ["Error: ", error] })) : servers.length === 0 ? (_jsx("text", { fg: "gray", children: "No servers found" })) : (_jsx(ScrollableList, { items: servers, selectedIndex: mcpRegistry.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight, getKey: (server) => server.name })), detailPanel: renderDetail() }));
|
|
226
226
|
}
|
|
227
227
|
export default McpRegistryScreen;
|
|
@@ -3,10 +3,11 @@ import { useApp, useModal, useNavigation } from "../state/AppContext.js";
|
|
|
3
3
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
4
4
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
5
5
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
import { searchMcpServers, formatDate } from "../../services/mcp-registry.js";
|
|
8
8
|
import { addMcpServer, setAllowMcp } from "../../services/claude-settings.js";
|
|
9
9
|
import type { McpRegistryServer, McpServerConfig } from "../../types/index.js";
|
|
10
|
+
import { ScrollableList } from "../components/ScrollableList.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Deduplicate servers by name, keeping only the latest version.
|
|
@@ -332,6 +333,7 @@ export function McpRegistryScreen() {
|
|
|
332
333
|
selectedIndex={mcpRegistry.selectedIndex}
|
|
333
334
|
renderItem={renderListItem}
|
|
334
335
|
maxHeight={dimensions.listPanelHeight}
|
|
336
|
+
getKey={(server) => server.name}
|
|
335
337
|
/>
|
|
336
338
|
)
|
|
337
339
|
}
|
|
@@ -4,10 +4,10 @@ import { useApp, useModal, useNavigation } from "../state/AppContext.js";
|
|
|
4
4
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
5
5
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
6
6
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
7
|
-
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
7
|
import { getMcpServersByCategory, getCategoryDisplayName, categoryOrder, } from "../../data/mcp-servers.js";
|
|
9
8
|
import { addMcpServer, removeMcpServer, getInstalledMcpServers, getEnabledMcpServers, } from "../../services/claude-settings.js";
|
|
10
9
|
import { renderMcpRow, renderMcpDetail, } from "../renderers/mcpRenderers.js";
|
|
10
|
+
import { ScrollableList } from "../components/ScrollableList.js";
|
|
11
11
|
export function McpScreen() {
|
|
12
12
|
const { state, dispatch } = useApp();
|
|
13
13
|
const { mcp } = state;
|