claudeup 3.17.0 → 4.0.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/ui/adapters/pluginsAdapter.js +139 -0
- package/src/ui/adapters/pluginsAdapter.ts +202 -0
- package/src/ui/adapters/settingsAdapter.js +111 -0
- package/src/ui/adapters/settingsAdapter.ts +165 -0
- package/src/ui/components/ScrollableList.js +4 -4
- package/src/ui/components/ScrollableList.tsx +4 -4
- package/src/ui/components/SearchInput.js +2 -2
- package/src/ui/components/SearchInput.tsx +3 -3
- package/src/ui/components/StyledText.js +1 -1
- package/src/ui/components/StyledText.tsx +5 -1
- package/src/ui/components/layout/ProgressBar.js +1 -1
- package/src/ui/components/layout/ProgressBar.tsx +1 -5
- package/src/ui/components/modals/InputModal.tsx +1 -6
- package/src/ui/components/modals/LoadingModal.js +1 -1
- package/src/ui/components/modals/LoadingModal.tsx +1 -3
- package/src/ui/hooks/index.js +3 -3
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useKeyboard.ts +1 -3
- package/src/ui/hooks/useKeyboardHandler.js +9 -9
- package/src/ui/hooks/useKeyboardHandler.ts +9 -9
- package/src/ui/renderers/cliToolRenderers.js +33 -0
- package/src/ui/renderers/cliToolRenderers.tsx +153 -0
- package/src/ui/renderers/mcpRenderers.js +26 -0
- package/src/ui/renderers/mcpRenderers.tsx +145 -0
- package/src/ui/renderers/pluginRenderers.js +124 -0
- package/src/ui/renderers/pluginRenderers.tsx +362 -0
- package/src/ui/renderers/profileRenderers.js +172 -0
- package/src/ui/renderers/profileRenderers.tsx +410 -0
- package/src/ui/renderers/settingsRenderers.js +69 -0
- package/src/ui/renderers/settingsRenderers.tsx +205 -0
- package/src/ui/screens/CliToolsScreen.js +14 -58
- package/src/ui/screens/CliToolsScreen.tsx +36 -196
- package/src/ui/screens/EnvVarsScreen.js +12 -168
- package/src/ui/screens/EnvVarsScreen.tsx +16 -327
- package/src/ui/screens/McpScreen.js +12 -62
- package/src/ui/screens/McpScreen.tsx +21 -190
- package/src/ui/screens/PluginsScreen.js +52 -425
- package/src/ui/screens/PluginsScreen.tsx +70 -758
- package/src/ui/screens/ProfilesScreen.js +32 -97
- package/src/ui/screens/ProfilesScreen.tsx +58 -328
- package/src/ui/screens/SkillsScreen.js +16 -16
- package/src/ui/screens/SkillsScreen.tsx +20 -23
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { useKeyboardHandler } from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useKeyboardHandler } from "../hooks/useKeyboardHandler.js";
|
|
3
3
|
|
|
4
4
|
interface SearchInputProps {
|
|
5
5
|
/** Current search value */
|
|
@@ -19,7 +19,7 @@ interface SearchInputProps {
|
|
|
19
19
|
export function SearchInput({
|
|
20
20
|
value,
|
|
21
21
|
onChange,
|
|
22
|
-
placeholder =
|
|
22
|
+
placeholder = "Search...",
|
|
23
23
|
isActive,
|
|
24
24
|
onExit,
|
|
25
25
|
onSubmit,
|
|
@@ -26,7 +26,7 @@ export function StyledText({ children, bold = false, color, bgColor, dim = false
|
|
|
26
26
|
}
|
|
27
27
|
if (inverse) {
|
|
28
28
|
// Inverse effect approximated with contrasting colors
|
|
29
|
-
content = _jsx("span", { fg: "black", bg: "white", children: content });
|
|
29
|
+
content = (_jsx("span", { fg: "black", bg: "white", children: content }));
|
|
30
30
|
}
|
|
31
31
|
// Apply colors (outer layer)
|
|
32
32
|
const textProps = {};
|
|
@@ -56,7 +56,11 @@ export function StyledText({
|
|
|
56
56
|
}
|
|
57
57
|
if (inverse) {
|
|
58
58
|
// Inverse effect approximated with contrasting colors
|
|
59
|
-
content =
|
|
59
|
+
content = (
|
|
60
|
+
<span fg="black" bg="white">
|
|
61
|
+
{content}
|
|
62
|
+
</span>
|
|
63
|
+
);
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
// Apply colors (outer layer)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
-
export function ProgressBar({ message, current, total
|
|
2
|
+
export function ProgressBar({ message, current, total }) {
|
|
3
3
|
const isDeterminate = current !== undefined && total !== undefined && total > 0;
|
|
4
4
|
if (isDeterminate) {
|
|
5
5
|
const barWidth = 20;
|
|
@@ -9,11 +9,7 @@ interface ProgressBarProps {
|
|
|
9
9
|
total?: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function ProgressBar({
|
|
13
|
-
message,
|
|
14
|
-
current,
|
|
15
|
-
total,
|
|
16
|
-
}: ProgressBarProps) {
|
|
12
|
+
export function ProgressBar({ message, current, total }: ProgressBarProps) {
|
|
17
13
|
const isDeterminate =
|
|
18
14
|
current !== undefined && total !== undefined && total > 0;
|
|
19
15
|
|
|
@@ -52,12 +52,7 @@ export function InputModal({
|
|
|
52
52
|
<text>{label}</text>
|
|
53
53
|
</box>
|
|
54
54
|
|
|
55
|
-
<input
|
|
56
|
-
value={value}
|
|
57
|
-
onChange={setValue}
|
|
58
|
-
focused
|
|
59
|
-
width={54}
|
|
60
|
-
/>
|
|
55
|
+
<input value={value} onChange={setValue} focused width={54} />
|
|
61
56
|
|
|
62
57
|
<box marginTop={1}>
|
|
63
58
|
<text fg="#666666">Enter to confirm • Escape to cancel</text>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from "react";
|
|
3
3
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
4
|
-
export function LoadingModal({ message
|
|
4
|
+
export function LoadingModal({ message }) {
|
|
5
5
|
const [frame, setFrame] = useState(0);
|
|
6
6
|
useEffect(() => {
|
|
7
7
|
const interval = setInterval(() => {
|
|
@@ -7,9 +7,7 @@ interface LoadingModalProps {
|
|
|
7
7
|
|
|
8
8
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
9
9
|
|
|
10
|
-
export function LoadingModal({
|
|
11
|
-
message,
|
|
12
|
-
}: LoadingModalProps) {
|
|
10
|
+
export function LoadingModal({ message }: LoadingModalProps) {
|
|
13
11
|
const [frame, setFrame] = useState(0);
|
|
14
12
|
|
|
15
13
|
useEffect(() => {
|
package/src/ui/hooks/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { useAsyncData, useDebouncedAsyncData } from
|
|
2
|
-
export { useKeyboardHandler } from
|
|
3
|
-
export { useKeyboard } from
|
|
1
|
+
export { useAsyncData, useDebouncedAsyncData } from "./useAsyncData.js";
|
|
2
|
+
export { useKeyboardHandler } from "./useKeyboardHandler.js";
|
|
3
|
+
export { useKeyboard } from "./useKeyboard.js";
|
package/src/ui/hooks/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { useAsyncData, useDebouncedAsyncData } from
|
|
2
|
-
export { useKeyboardHandler } from
|
|
3
|
-
export { useKeyboard } from
|
|
1
|
+
export { useAsyncData, useDebouncedAsyncData } from "./useAsyncData.js";
|
|
2
|
+
export { useKeyboardHandler } from "./useKeyboardHandler.js";
|
|
3
|
+
export { useKeyboard } from "./useKeyboard.js";
|
|
@@ -19,8 +19,6 @@ export interface KeyEvent {
|
|
|
19
19
|
* - Arrow keys: "up", "down", "left", "right"
|
|
20
20
|
* - Special: "enter", "escape", "tab", "backspace", "delete"
|
|
21
21
|
*/
|
|
22
|
-
export function useKeyboard(
|
|
23
|
-
handler: (event: KeyEvent) => void,
|
|
24
|
-
): void {
|
|
22
|
+
export function useKeyboard(handler: (event: KeyEvent) => void): void {
|
|
25
23
|
useOpenTUIKeyboard(handler);
|
|
26
24
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useKeyboard as useOpenTUIKeyboard } from
|
|
1
|
+
import { useKeyboard as useOpenTUIKeyboard } from "@opentui/react";
|
|
2
2
|
/**
|
|
3
3
|
* useKeyboardHandler hook
|
|
4
4
|
* Provides Ink-like API for keyboard events
|
|
@@ -23,17 +23,17 @@ export function useKeyboardHandler(handler) {
|
|
|
23
23
|
ctrl: event.ctrl || false,
|
|
24
24
|
shift: event.shift || false,
|
|
25
25
|
meta: event.meta || false,
|
|
26
|
-
return: event.name ===
|
|
27
|
-
upArrow: event.name ===
|
|
28
|
-
downArrow: event.name ===
|
|
29
|
-
leftArrow: event.name ===
|
|
30
|
-
rightArrow: event.name ===
|
|
31
|
-
escape: event.name ===
|
|
32
|
-
tab: event.name ===
|
|
26
|
+
return: event.name === "enter" || event.name === "return",
|
|
27
|
+
upArrow: event.name === "up",
|
|
28
|
+
downArrow: event.name === "down",
|
|
29
|
+
leftArrow: event.name === "left",
|
|
30
|
+
rightArrow: event.name === "right",
|
|
31
|
+
escape: event.name === "escape",
|
|
32
|
+
tab: event.name === "tab",
|
|
33
33
|
};
|
|
34
34
|
// For printable characters, pass the character as input
|
|
35
35
|
// For special keys, pass empty string
|
|
36
|
-
const input = event.name.length === 1 ? event.name :
|
|
36
|
+
const input = event.name.length === 1 ? event.name : "";
|
|
37
37
|
handler(input, key);
|
|
38
38
|
});
|
|
39
39
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useKeyboard as useOpenTUIKeyboard } from
|
|
1
|
+
import { useKeyboard as useOpenTUIKeyboard } from "@opentui/react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Wrapper hook for OpenTUI's useKeyboard that provides an Ink-like API
|
|
@@ -45,18 +45,18 @@ export function useKeyboardHandler(handler: KeyHandler): void {
|
|
|
45
45
|
ctrl: event.ctrl || false,
|
|
46
46
|
shift: event.shift || false,
|
|
47
47
|
meta: event.meta || false,
|
|
48
|
-
return: event.name ===
|
|
49
|
-
upArrow: event.name ===
|
|
50
|
-
downArrow: event.name ===
|
|
51
|
-
leftArrow: event.name ===
|
|
52
|
-
rightArrow: event.name ===
|
|
53
|
-
escape: event.name ===
|
|
54
|
-
tab: event.name ===
|
|
48
|
+
return: event.name === "enter" || event.name === "return",
|
|
49
|
+
upArrow: event.name === "up",
|
|
50
|
+
downArrow: event.name === "down",
|
|
51
|
+
leftArrow: event.name === "left",
|
|
52
|
+
rightArrow: event.name === "right",
|
|
53
|
+
escape: event.name === "escape",
|
|
54
|
+
tab: event.name === "tab",
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
// For printable characters, pass the character as input
|
|
58
58
|
// For special keys, pass empty string
|
|
59
|
-
const input = event.name.length === 1 ? event.name :
|
|
59
|
+
const input = event.name.length === 1 ? event.name : "";
|
|
60
60
|
|
|
61
61
|
handler(input, key);
|
|
62
62
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
+
import { theme } from "../theme.js";
|
|
3
|
+
// ─── Row renderer ──────────────────────────────────────────────────────────────
|
|
4
|
+
export function renderCliToolRow(status, _index, isSelected) {
|
|
5
|
+
const { tool, installed, installedVersion, hasUpdate, checking } = status;
|
|
6
|
+
let icon;
|
|
7
|
+
let iconColor;
|
|
8
|
+
if (!installed) {
|
|
9
|
+
icon = "○";
|
|
10
|
+
iconColor = theme.colors.muted;
|
|
11
|
+
}
|
|
12
|
+
else if (hasUpdate) {
|
|
13
|
+
icon = "⬆";
|
|
14
|
+
iconColor = theme.colors.warning;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
icon = "●";
|
|
18
|
+
iconColor = theme.colors.success;
|
|
19
|
+
}
|
|
20
|
+
const versionText = installedVersion ? `v${installedVersion}` : "";
|
|
21
|
+
if (isSelected) {
|
|
22
|
+
return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", icon, " ", tool.displayName, " ", versionText, checking ? "..." : "", " "] }));
|
|
23
|
+
}
|
|
24
|
+
return (_jsxs("text", { children: [_jsx("span", { fg: iconColor, children: icon }), _jsxs("span", { fg: theme.colors.text, children: [" ", tool.displayName] }), versionText ? _jsxs("span", { fg: theme.colors.success, children: [" ", versionText] }) : null, checking ? _jsx("span", { fg: theme.colors.muted, children: "..." }) : null] }));
|
|
25
|
+
}
|
|
26
|
+
// ─── Detail renderer ───────────────────────────────────────────────────────────
|
|
27
|
+
export function renderCliToolDetail(status) {
|
|
28
|
+
if (!status) {
|
|
29
|
+
return (_jsx("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx("text", { fg: theme.colors.muted, children: "Select a tool to see details" }) }));
|
|
30
|
+
}
|
|
31
|
+
const { tool, installed, installedVersion, latestVersion, hasUpdate, checking } = status;
|
|
32
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: theme.colors.info, children: _jsxs("strong", { children: ["⚙ ", tool.displayName] }) }), hasUpdate ? _jsx("text", { fg: theme.colors.warning, children: " \u2B06" }) : null] }), _jsx("text", { fg: theme.colors.muted, children: tool.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Status " }), !installed ? (_jsx("text", { fg: theme.colors.muted, children: "○ Not installed" })) : checking ? (_jsx("text", { fg: theme.colors.success, children: "● Checking..." })) : hasUpdate ? (_jsx("text", { fg: theme.colors.warning, children: "● Update available" })) : (_jsx("text", { fg: theme.colors.success, children: "● Up to date" }))] }), installedVersion ? (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Installed " }), _jsxs("text", { fg: theme.colors.success, children: ["v", installedVersion] })] })) : null, latestVersion ? (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Latest " }), _jsxs("text", { fg: theme.colors.text, children: ["v", latestVersion] })] })) : null, _jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Website " }), _jsx("text", { fg: theme.colors.link, children: tool.website })] })] }), _jsx("box", { marginTop: 2, children: !installed ? (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.success, fg: "black", children: [" ", "Enter", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Install" })] })) : hasUpdate ? (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.warning, fg: "black", children: [" ", "Enter", " "] }), _jsxs("text", { fg: theme.colors.muted, children: [" Update to v", latestVersion] })] })) : (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.muted, fg: "white", children: [" ", "Enter", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Reinstall" })] })) })] }));
|
|
33
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { CliTool } from "../../data/cli-tools.js";
|
|
3
|
+
import { theme } from "../theme.js";
|
|
4
|
+
|
|
5
|
+
// ─── Status type ───────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export interface CliToolStatus {
|
|
8
|
+
tool: CliTool;
|
|
9
|
+
installed: boolean;
|
|
10
|
+
installedVersion?: string;
|
|
11
|
+
latestVersion?: string;
|
|
12
|
+
hasUpdate?: boolean;
|
|
13
|
+
checking: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ─── Row renderer ──────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export function renderCliToolRow(
|
|
19
|
+
status: CliToolStatus,
|
|
20
|
+
_index: number,
|
|
21
|
+
isSelected: boolean,
|
|
22
|
+
): React.ReactNode {
|
|
23
|
+
const { tool, installed, installedVersion, hasUpdate, checking } = status;
|
|
24
|
+
|
|
25
|
+
let icon: string;
|
|
26
|
+
let iconColor: string;
|
|
27
|
+
|
|
28
|
+
if (!installed) {
|
|
29
|
+
icon = "○";
|
|
30
|
+
iconColor = theme.colors.muted;
|
|
31
|
+
} else if (hasUpdate) {
|
|
32
|
+
icon = "⬆";
|
|
33
|
+
iconColor = theme.colors.warning;
|
|
34
|
+
} else {
|
|
35
|
+
icon = "●";
|
|
36
|
+
iconColor = theme.colors.success;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const versionText = installedVersion ? `v${installedVersion}` : "";
|
|
40
|
+
|
|
41
|
+
if (isSelected) {
|
|
42
|
+
return (
|
|
43
|
+
<text bg={theme.selection.bg} fg={theme.selection.fg}>
|
|
44
|
+
{" "}
|
|
45
|
+
{icon} {tool.displayName} {versionText}
|
|
46
|
+
{checking ? "..." : ""}{" "}
|
|
47
|
+
</text>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<text>
|
|
53
|
+
<span fg={iconColor}>{icon}</span>
|
|
54
|
+
<span fg={theme.colors.text}> {tool.displayName}</span>
|
|
55
|
+
{versionText ? <span fg={theme.colors.success}> {versionText}</span> : null}
|
|
56
|
+
{checking ? <span fg={theme.colors.muted}>{"..."}</span> : null}
|
|
57
|
+
</text>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Detail renderer ───────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export function renderCliToolDetail(
|
|
64
|
+
status: CliToolStatus | undefined,
|
|
65
|
+
): React.ReactNode {
|
|
66
|
+
if (!status) {
|
|
67
|
+
return (
|
|
68
|
+
<box
|
|
69
|
+
flexDirection="column"
|
|
70
|
+
alignItems="center"
|
|
71
|
+
justifyContent="center"
|
|
72
|
+
flexGrow={1}
|
|
73
|
+
>
|
|
74
|
+
<text fg={theme.colors.muted}>Select a tool to see details</text>
|
|
75
|
+
</box>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { tool, installed, installedVersion, latestVersion, hasUpdate, checking } =
|
|
80
|
+
status;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<box flexDirection="column">
|
|
84
|
+
<box marginBottom={1}>
|
|
85
|
+
<text fg={theme.colors.info}>
|
|
86
|
+
<strong>{"⚙ "}{tool.displayName}</strong>
|
|
87
|
+
</text>
|
|
88
|
+
{hasUpdate ? <text fg={theme.colors.warning}> ⬆</text> : null}
|
|
89
|
+
</box>
|
|
90
|
+
|
|
91
|
+
<text fg={theme.colors.muted}>{tool.description}</text>
|
|
92
|
+
|
|
93
|
+
<box marginTop={1} flexDirection="column">
|
|
94
|
+
<box>
|
|
95
|
+
<text fg={theme.colors.muted}>{"Status "}</text>
|
|
96
|
+
{!installed ? (
|
|
97
|
+
<text fg={theme.colors.muted}>{"○ Not installed"}</text>
|
|
98
|
+
) : checking ? (
|
|
99
|
+
<text fg={theme.colors.success}>{"● Checking..."}</text>
|
|
100
|
+
) : hasUpdate ? (
|
|
101
|
+
<text fg={theme.colors.warning}>{"● Update available"}</text>
|
|
102
|
+
) : (
|
|
103
|
+
<text fg={theme.colors.success}>{"● Up to date"}</text>
|
|
104
|
+
)}
|
|
105
|
+
</box>
|
|
106
|
+
{installedVersion ? (
|
|
107
|
+
<box>
|
|
108
|
+
<text fg={theme.colors.muted}>{"Installed "}</text>
|
|
109
|
+
<text fg={theme.colors.success}>v{installedVersion}</text>
|
|
110
|
+
</box>
|
|
111
|
+
) : null}
|
|
112
|
+
{latestVersion ? (
|
|
113
|
+
<box>
|
|
114
|
+
<text fg={theme.colors.muted}>{"Latest "}</text>
|
|
115
|
+
<text fg={theme.colors.text}>v{latestVersion}</text>
|
|
116
|
+
</box>
|
|
117
|
+
) : null}
|
|
118
|
+
<box>
|
|
119
|
+
<text fg={theme.colors.muted}>{"Website "}</text>
|
|
120
|
+
<text fg={theme.colors.link}>{tool.website}</text>
|
|
121
|
+
</box>
|
|
122
|
+
</box>
|
|
123
|
+
|
|
124
|
+
<box marginTop={2}>
|
|
125
|
+
{!installed ? (
|
|
126
|
+
<box>
|
|
127
|
+
<text bg={theme.colors.success} fg="black">
|
|
128
|
+
{" "}
|
|
129
|
+
Enter{" "}
|
|
130
|
+
</text>
|
|
131
|
+
<text fg={theme.colors.muted}> Install</text>
|
|
132
|
+
</box>
|
|
133
|
+
) : hasUpdate ? (
|
|
134
|
+
<box>
|
|
135
|
+
<text bg={theme.colors.warning} fg="black">
|
|
136
|
+
{" "}
|
|
137
|
+
Enter{" "}
|
|
138
|
+
</text>
|
|
139
|
+
<text fg={theme.colors.muted}> Update to v{latestVersion}</text>
|
|
140
|
+
</box>
|
|
141
|
+
) : (
|
|
142
|
+
<box>
|
|
143
|
+
<text bg={theme.colors.muted} fg="white">
|
|
144
|
+
{" "}
|
|
145
|
+
Enter{" "}
|
|
146
|
+
</text>
|
|
147
|
+
<text fg={theme.colors.muted}> Reinstall</text>
|
|
148
|
+
</box>
|
|
149
|
+
)}
|
|
150
|
+
</box>
|
|
151
|
+
</box>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
+
import { theme } from "../theme.js";
|
|
3
|
+
// ─── Row renderers ─────────────────────────────────────────────────────────────
|
|
4
|
+
export function renderMcpRow(item, _index, isSelected) {
|
|
5
|
+
if (item.kind === "category") {
|
|
6
|
+
return (_jsx("text", { fg: theme.selection.bg, children: _jsxs("strong", { children: ["▸ ", item.label] }) }));
|
|
7
|
+
}
|
|
8
|
+
const { server, isInstalled } = item;
|
|
9
|
+
const icon = isInstalled ? "●" : "○";
|
|
10
|
+
const iconColor = isInstalled ? theme.colors.success : theme.colors.muted;
|
|
11
|
+
if (isSelected) {
|
|
12
|
+
return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", icon, " ", server.name, " "] }));
|
|
13
|
+
}
|
|
14
|
+
return (_jsxs("text", { children: [_jsx("span", { fg: iconColor, children: icon }), _jsxs("span", { fg: theme.colors.text, children: [" ", server.name] }), server.requiresConfig ? (_jsx("span", { fg: theme.colors.warning, children: " \u2699" })) : null] }));
|
|
15
|
+
}
|
|
16
|
+
// ─── Detail renderer ───────────────────────────────────────────────────────────
|
|
17
|
+
export function renderMcpDetail(item, loading) {
|
|
18
|
+
if (loading) {
|
|
19
|
+
return _jsx("text", { fg: theme.colors.muted, children: "Loading MCP servers..." });
|
|
20
|
+
}
|
|
21
|
+
if (!item || item.kind === "category") {
|
|
22
|
+
return (_jsx("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx("text", { fg: theme.colors.muted, children: "Select a server to see details" }) }));
|
|
23
|
+
}
|
|
24
|
+
const { server, isInstalled } = item;
|
|
25
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: theme.colors.info, children: _jsxs("strong", { children: ["⚡ ", server.name] }) }), server.requiresConfig ? (_jsx("text", { fg: theme.colors.warning, children: " \u2699" })) : null] }), _jsx("text", { fg: theme.colors.muted, children: server.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Status " }), isInstalled ? (_jsx("text", { fg: theme.colors.success, children: "● Installed" })) : (_jsx("text", { fg: theme.colors.muted, children: "○ Not installed" }))] }), _jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Type " }), _jsx("text", { fg: theme.colors.text, children: server.type === "http" ? "HTTP" : "Command" })] }), server.type === "http" ? (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "URL " }), _jsx("text", { fg: theme.colors.link, children: server.url })] })) : (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Command " }), _jsx("text", { fg: theme.colors.info, children: server.command })] })), server.requiresConfig ? (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Config " }), _jsxs("text", { fg: theme.colors.warning, children: [server.configFields?.length ?? 0, " fields required"] })] })) : null] }), _jsx("box", { marginTop: 2, children: isInstalled ? (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.danger, fg: "white", children: [" ", "Enter", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Remove server" })] })) : (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.success, fg: "black", children: [" ", "Enter", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Install server" })] })) })] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { McpServer } from "../../types/index.js";
|
|
3
|
+
import { theme } from "../theme.js";
|
|
4
|
+
|
|
5
|
+
// ─── List item types ───────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export type McpListItem =
|
|
8
|
+
| { kind: "category"; label: string }
|
|
9
|
+
| { kind: "server"; server: McpServer; isInstalled: boolean };
|
|
10
|
+
|
|
11
|
+
// ─── Row renderers ─────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export function renderMcpRow(
|
|
14
|
+
item: McpListItem,
|
|
15
|
+
_index: number,
|
|
16
|
+
isSelected: boolean,
|
|
17
|
+
): React.ReactNode {
|
|
18
|
+
if (item.kind === "category") {
|
|
19
|
+
return (
|
|
20
|
+
<text fg={theme.selection.bg}>
|
|
21
|
+
<strong>{"▸ "}{item.label}</strong>
|
|
22
|
+
</text>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { server, isInstalled } = item;
|
|
27
|
+
const icon = isInstalled ? "●" : "○";
|
|
28
|
+
const iconColor = isInstalled ? theme.colors.success : theme.colors.muted;
|
|
29
|
+
|
|
30
|
+
if (isSelected) {
|
|
31
|
+
return (
|
|
32
|
+
<text bg={theme.selection.bg} fg={theme.selection.fg}>
|
|
33
|
+
{" "}
|
|
34
|
+
{icon} {server.name}{" "}
|
|
35
|
+
</text>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<text>
|
|
41
|
+
<span fg={iconColor}>{icon}</span>
|
|
42
|
+
<span fg={theme.colors.text}> {server.name}</span>
|
|
43
|
+
{server.requiresConfig ? (
|
|
44
|
+
<span fg={theme.colors.warning}> ⚙</span>
|
|
45
|
+
) : null}
|
|
46
|
+
</text>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Detail renderer ───────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export function renderMcpDetail(
|
|
53
|
+
item: McpListItem | undefined,
|
|
54
|
+
loading: boolean,
|
|
55
|
+
): React.ReactNode {
|
|
56
|
+
if (loading) {
|
|
57
|
+
return <text fg={theme.colors.muted}>Loading MCP servers...</text>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!item || item.kind === "category") {
|
|
61
|
+
return (
|
|
62
|
+
<box
|
|
63
|
+
flexDirection="column"
|
|
64
|
+
alignItems="center"
|
|
65
|
+
justifyContent="center"
|
|
66
|
+
flexGrow={1}
|
|
67
|
+
>
|
|
68
|
+
<text fg={theme.colors.muted}>Select a server to see details</text>
|
|
69
|
+
</box>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { server, isInstalled } = item;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<box flexDirection="column">
|
|
77
|
+
<box marginBottom={1}>
|
|
78
|
+
<text fg={theme.colors.info}>
|
|
79
|
+
<strong>{"⚡ "}{server.name}</strong>
|
|
80
|
+
</text>
|
|
81
|
+
{server.requiresConfig ? (
|
|
82
|
+
<text fg={theme.colors.warning}> ⚙</text>
|
|
83
|
+
) : null}
|
|
84
|
+
</box>
|
|
85
|
+
|
|
86
|
+
<text fg={theme.colors.muted}>{server.description}</text>
|
|
87
|
+
|
|
88
|
+
<box marginTop={1} flexDirection="column">
|
|
89
|
+
<box>
|
|
90
|
+
<text fg={theme.colors.muted}>{"Status "}</text>
|
|
91
|
+
{isInstalled ? (
|
|
92
|
+
<text fg={theme.colors.success}>{"● Installed"}</text>
|
|
93
|
+
) : (
|
|
94
|
+
<text fg={theme.colors.muted}>{"○ Not installed"}</text>
|
|
95
|
+
)}
|
|
96
|
+
</box>
|
|
97
|
+
<box>
|
|
98
|
+
<text fg={theme.colors.muted}>{"Type "}</text>
|
|
99
|
+
<text fg={theme.colors.text}>
|
|
100
|
+
{server.type === "http" ? "HTTP" : "Command"}
|
|
101
|
+
</text>
|
|
102
|
+
</box>
|
|
103
|
+
{server.type === "http" ? (
|
|
104
|
+
<box>
|
|
105
|
+
<text fg={theme.colors.muted}>{"URL "}</text>
|
|
106
|
+
<text fg={theme.colors.link}>{server.url}</text>
|
|
107
|
+
</box>
|
|
108
|
+
) : (
|
|
109
|
+
<box>
|
|
110
|
+
<text fg={theme.colors.muted}>{"Command "}</text>
|
|
111
|
+
<text fg={theme.colors.info}>{server.command}</text>
|
|
112
|
+
</box>
|
|
113
|
+
)}
|
|
114
|
+
{server.requiresConfig ? (
|
|
115
|
+
<box>
|
|
116
|
+
<text fg={theme.colors.muted}>{"Config "}</text>
|
|
117
|
+
<text fg={theme.colors.warning}>
|
|
118
|
+
{server.configFields?.length ?? 0} fields required
|
|
119
|
+
</text>
|
|
120
|
+
</box>
|
|
121
|
+
) : null}
|
|
122
|
+
</box>
|
|
123
|
+
|
|
124
|
+
<box marginTop={2}>
|
|
125
|
+
{isInstalled ? (
|
|
126
|
+
<box>
|
|
127
|
+
<text bg={theme.colors.danger} fg="white">
|
|
128
|
+
{" "}
|
|
129
|
+
Enter{" "}
|
|
130
|
+
</text>
|
|
131
|
+
<text fg={theme.colors.muted}> Remove server</text>
|
|
132
|
+
</box>
|
|
133
|
+
) : (
|
|
134
|
+
<box>
|
|
135
|
+
<text bg={theme.colors.success} fg="black">
|
|
136
|
+
{" "}
|
|
137
|
+
Enter{" "}
|
|
138
|
+
</text>
|
|
139
|
+
<text fg={theme.colors.muted}> Install server</text>
|
|
140
|
+
</box>
|
|
141
|
+
)}
|
|
142
|
+
</box>
|
|
143
|
+
</box>
|
|
144
|
+
);
|
|
145
|
+
}
|