wave-code 0.14.0 → 0.14.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/dist/components/BackgroundTaskManager.d.ts.map +1 -1
- package/dist/components/BackgroundTaskManager.js +36 -25
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +2 -2
- package/dist/components/MarketplaceAddForm.d.ts.map +1 -1
- package/dist/components/MarketplaceAddForm.js +48 -17
- package/dist/components/MarketplaceDetail.d.ts.map +1 -1
- package/dist/components/MarketplaceDetail.js +2 -1
- package/dist/components/MarketplaceList.d.ts.map +1 -1
- package/dist/components/MarketplaceList.js +2 -1
- package/dist/components/McpManager.d.ts.map +1 -1
- package/dist/components/McpManager.js +22 -27
- package/dist/components/PluginDetail.d.ts.map +1 -1
- package/dist/components/PluginDetail.js +22 -22
- package/dist/components/PluginManagerShell.d.ts +1 -0
- package/dist/components/PluginManagerShell.d.ts.map +1 -1
- package/dist/components/PluginManagerShell.js +2 -2
- package/dist/components/PluginManagerTypes.d.ts +2 -2
- package/dist/components/PluginManagerTypes.d.ts.map +1 -1
- package/dist/contexts/useChat.d.ts +1 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +125 -98
- package/dist/hooks/usePluginManager.d.ts +3 -1
- package/dist/hooks/usePluginManager.d.ts.map +1 -1
- package/dist/hooks/usePluginManager.js +16 -7
- package/dist/reducers/backgroundTaskManagerReducer.d.ts +43 -0
- package/dist/reducers/backgroundTaskManagerReducer.d.ts.map +1 -0
- package/dist/reducers/backgroundTaskManagerReducer.js +23 -0
- package/dist/reducers/marketplaceAddFormReducer.d.ts +24 -0
- package/dist/reducers/marketplaceAddFormReducer.d.ts.map +1 -0
- package/dist/reducers/marketplaceAddFormReducer.js +18 -0
- package/dist/reducers/mcpManagerReducer.d.ts +16 -0
- package/dist/reducers/mcpManagerReducer.d.ts.map +1 -0
- package/dist/reducers/mcpManagerReducer.js +15 -0
- package/dist/reducers/pluginDetailReducer.d.ts +25 -0
- package/dist/reducers/pluginDetailReducer.d.ts.map +1 -0
- package/dist/reducers/pluginDetailReducer.js +38 -0
- package/package.json +2 -2
- package/src/components/BackgroundTaskManager.tsx +32 -34
- package/src/components/InputBox.tsx +7 -1
- package/src/components/MarketplaceAddForm.tsx +76 -22
- package/src/components/MarketplaceDetail.tsx +4 -0
- package/src/components/MarketplaceList.tsx +4 -0
- package/src/components/McpManager.tsx +30 -34
- package/src/components/PluginDetail.tsx +25 -33
- package/src/components/PluginManagerShell.tsx +3 -2
- package/src/components/PluginManagerTypes.ts +8 -2
- package/src/contexts/useChat.tsx +54 -20
- package/src/hooks/usePluginManager.ts +18 -7
- package/src/reducers/backgroundTaskManagerReducer.ts +60 -0
- package/src/reducers/marketplaceAddFormReducer.ts +35 -0
- package/src/reducers/mcpManagerReducer.ts +31 -0
- package/src/reducers/pluginDetailReducer.ts +58 -0
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useReducer } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { useChat } from "../contexts/useChat.js";
|
|
4
4
|
import { getLastLines } from "wave-agent-sdk";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
description?: string;
|
|
10
|
-
status: "running" | "completed" | "failed" | "killed";
|
|
11
|
-
startTime: number;
|
|
12
|
-
exitCode?: number;
|
|
13
|
-
runtime?: number;
|
|
14
|
-
outputPath?: string;
|
|
15
|
-
}
|
|
5
|
+
import {
|
|
6
|
+
backgroundTaskManagerReducer,
|
|
7
|
+
type BackgroundTaskManagerState,
|
|
8
|
+
} from "../reducers/backgroundTaskManagerReducer.js";
|
|
16
9
|
|
|
17
10
|
export interface BackgroundTaskManagerProps {
|
|
18
11
|
onCancel: () => void;
|
|
@@ -23,22 +16,23 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
|
|
|
23
16
|
}) => {
|
|
24
17
|
const { backgroundTasks, getBackgroundTaskOutput, stopBackgroundTask } =
|
|
25
18
|
useChat();
|
|
26
|
-
const [tasks, setTasks] = useState<Task[]>([]);
|
|
27
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
28
19
|
const MAX_VISIBLE_ITEMS = 3;
|
|
29
|
-
|
|
30
|
-
const [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
20
|
+
|
|
21
|
+
const [state, dispatch] = useReducer(backgroundTaskManagerReducer, {
|
|
22
|
+
tasks: [],
|
|
23
|
+
selectedIndex: 0,
|
|
24
|
+
viewMode: "list",
|
|
25
|
+
detailTaskId: null,
|
|
26
|
+
detailOutput: null,
|
|
27
|
+
} as BackgroundTaskManagerState);
|
|
28
|
+
|
|
29
|
+
const { tasks, selectedIndex, viewMode, detailTaskId, detailOutput } = state;
|
|
37
30
|
|
|
38
31
|
// Convert backgroundTasks to local Task format
|
|
39
32
|
useEffect(() => {
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
dispatch({
|
|
34
|
+
type: "SET_TASKS",
|
|
35
|
+
tasks: backgroundTasks.map((task) => ({
|
|
42
36
|
id: task.id,
|
|
43
37
|
type: task.type,
|
|
44
38
|
description: task.description,
|
|
@@ -48,14 +42,14 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
|
|
|
48
42
|
runtime: task.runtime,
|
|
49
43
|
outputPath: task.outputPath,
|
|
50
44
|
})),
|
|
51
|
-
);
|
|
45
|
+
});
|
|
52
46
|
}, [backgroundTasks]);
|
|
53
47
|
|
|
54
48
|
// Load detail output for selected task
|
|
55
49
|
useEffect(() => {
|
|
56
50
|
if (viewMode === "detail" && detailTaskId) {
|
|
57
51
|
const output = getBackgroundTaskOutput(detailTaskId);
|
|
58
|
-
|
|
52
|
+
dispatch({ type: "SET_DETAIL_OUTPUT", output });
|
|
59
53
|
}
|
|
60
54
|
}, [viewMode, detailTaskId, getBackgroundTaskOutput]);
|
|
61
55
|
|
|
@@ -91,8 +85,8 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
|
|
|
91
85
|
if (key.return) {
|
|
92
86
|
if (tasks.length > 0 && selectedIndex < tasks.length) {
|
|
93
87
|
const selectedTask = tasks[selectedIndex];
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
dispatch({ type: "SET_DETAIL_TASK_ID", id: selectedTask.id });
|
|
89
|
+
dispatch({ type: "SET_VIEW_MODE", mode: "detail" });
|
|
96
90
|
}
|
|
97
91
|
return;
|
|
98
92
|
}
|
|
@@ -103,12 +97,18 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
|
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
if (key.upArrow) {
|
|
106
|
-
|
|
100
|
+
dispatch({
|
|
101
|
+
type: "SELECT_INDEX",
|
|
102
|
+
index: Math.max(0, selectedIndex - 1),
|
|
103
|
+
});
|
|
107
104
|
return;
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
if (key.downArrow) {
|
|
111
|
-
|
|
108
|
+
dispatch({
|
|
109
|
+
type: "SELECT_INDEX",
|
|
110
|
+
index: Math.min(tasks.length - 1, selectedIndex + 1),
|
|
111
|
+
});
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -122,9 +122,7 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
|
|
|
122
122
|
} else if (viewMode === "detail") {
|
|
123
123
|
// Detail mode navigation
|
|
124
124
|
if (key.escape) {
|
|
125
|
-
|
|
126
|
-
setDetailTaskId(null);
|
|
127
|
-
setDetailOutput(null);
|
|
125
|
+
dispatch({ type: "RESET_DETAIL" });
|
|
128
126
|
return;
|
|
129
127
|
}
|
|
130
128
|
|
|
@@ -141,7 +139,7 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
|
|
|
141
139
|
if (viewMode === "detail" && detailTaskId && detailOutput) {
|
|
142
140
|
const task = tasks.find((t) => t.id === detailTaskId);
|
|
143
141
|
if (!task) {
|
|
144
|
-
|
|
142
|
+
dispatch({ type: "RESET_DETAIL" });
|
|
145
143
|
return null;
|
|
146
144
|
}
|
|
147
145
|
|
|
@@ -68,6 +68,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
68
68
|
currentModel,
|
|
69
69
|
configuredModels,
|
|
70
70
|
setModel,
|
|
71
|
+
recreateAgent,
|
|
71
72
|
} = useChat();
|
|
72
73
|
|
|
73
74
|
// Input manager with all input state and functionality (including images)
|
|
@@ -205,7 +206,12 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
if (showPluginManager) {
|
|
208
|
-
return
|
|
209
|
+
return (
|
|
210
|
+
<PluginManagerShell
|
|
211
|
+
onCancel={() => setShowPluginManager(false)}
|
|
212
|
+
onPluginInstalled={recreateAgent}
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
209
215
|
}
|
|
210
216
|
|
|
211
217
|
if (showModelSelector) {
|
|
@@ -1,53 +1,107 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useReducer } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
+
import { marketplaceAddFormReducer } from "../reducers/marketplaceAddFormReducer.js";
|
|
5
|
+
|
|
6
|
+
const SCOPES = [
|
|
7
|
+
{ value: "user" as const, label: "user" },
|
|
8
|
+
{ value: "project" as const, label: "project" },
|
|
9
|
+
{ value: "local" as const, label: "local" },
|
|
10
|
+
];
|
|
4
11
|
|
|
5
12
|
export const MarketplaceAddForm: React.FC = () => {
|
|
6
13
|
const { state, actions } = usePluginManagerContext();
|
|
7
|
-
const [source,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
sourceRef.current = source;
|
|
13
|
-
}, [source]);
|
|
14
|
+
const [{ source, scopeIndex, step }, dispatch] = useReducer(
|
|
15
|
+
marketplaceAddFormReducer,
|
|
16
|
+
{ source: "", scopeIndex: 0, step: "source" },
|
|
17
|
+
);
|
|
14
18
|
|
|
15
19
|
useInput((input, key) => {
|
|
16
20
|
if (key.escape) {
|
|
17
|
-
|
|
21
|
+
if (step === "scope") {
|
|
22
|
+
dispatch({ type: "BACK_TO_SOURCE" });
|
|
23
|
+
} else {
|
|
24
|
+
actions.setView("MARKETPLACES");
|
|
25
|
+
}
|
|
18
26
|
} else if (state.isLoading) {
|
|
19
27
|
return;
|
|
20
|
-
} else if (key.return) {
|
|
21
|
-
if (
|
|
22
|
-
|
|
28
|
+
} else if (step === "source" && key.return) {
|
|
29
|
+
if (source.trim()) {
|
|
30
|
+
dispatch({ type: "SET_STEP", step: "scope" });
|
|
31
|
+
}
|
|
32
|
+
} else if (step === "source" && (key.backspace || key.delete)) {
|
|
33
|
+
dispatch({ type: "DELETE_CHAR" });
|
|
34
|
+
} else if (
|
|
35
|
+
step === "source" &&
|
|
36
|
+
input &&
|
|
37
|
+
!key.ctrl &&
|
|
38
|
+
!key.meta &&
|
|
39
|
+
!("alt" in key && key.alt)
|
|
40
|
+
) {
|
|
41
|
+
dispatch({ type: "INSERT_CHAR", text: input });
|
|
42
|
+
} else if (step === "scope") {
|
|
43
|
+
if (key.upArrow) {
|
|
44
|
+
dispatch({
|
|
45
|
+
type: "SET_SCOPE_INDEX",
|
|
46
|
+
index: Math.max(0, scopeIndex - 1),
|
|
47
|
+
});
|
|
48
|
+
} else if (key.downArrow) {
|
|
49
|
+
dispatch({
|
|
50
|
+
type: "SET_SCOPE_INDEX",
|
|
51
|
+
index: Math.min(SCOPES.length - 1, scopeIndex + 1),
|
|
52
|
+
});
|
|
53
|
+
} else if (key.return) {
|
|
54
|
+
const scope = SCOPES[scopeIndex].value;
|
|
55
|
+
actions.addMarketplace(source.trim(), scope);
|
|
23
56
|
}
|
|
24
|
-
} else if (key.backspace || key.delete) {
|
|
25
|
-
setSource((prev) => prev.slice(0, -1));
|
|
26
|
-
} else if (input.length === 1) {
|
|
27
|
-
setSource((prev) => prev + input);
|
|
28
57
|
}
|
|
29
58
|
});
|
|
30
59
|
|
|
60
|
+
if (step === "source") {
|
|
61
|
+
return (
|
|
62
|
+
<Box flexDirection="column" padding={1}>
|
|
63
|
+
<Text bold color="cyan">
|
|
64
|
+
Step 1/2: Enter marketplace source
|
|
65
|
+
</Text>
|
|
66
|
+
<Box marginTop={1}>
|
|
67
|
+
<Text>Source: </Text>
|
|
68
|
+
<Text color="yellow">{source}</Text>
|
|
69
|
+
{!state.isLoading && <Text color="yellow">_</Text>}
|
|
70
|
+
</Box>
|
|
71
|
+
<Box marginTop={1}>
|
|
72
|
+
<Text dimColor>Enter to continue, Esc to cancel</Text>
|
|
73
|
+
</Box>
|
|
74
|
+
</Box>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
31
78
|
return (
|
|
32
79
|
<Box flexDirection="column" padding={1}>
|
|
33
80
|
<Text bold color="cyan">
|
|
34
|
-
|
|
81
|
+
Step 2/2: Select scope
|
|
35
82
|
</Text>
|
|
36
83
|
<Box marginTop={1}>
|
|
37
|
-
<Text>Source
|
|
38
|
-
<Text
|
|
39
|
-
|
|
84
|
+
<Text dimColor>Source: </Text>
|
|
85
|
+
<Text dimColor>{source}</Text>
|
|
86
|
+
</Box>
|
|
87
|
+
<Box marginTop={1} flexDirection="column">
|
|
88
|
+
{SCOPES.map((s, i) => (
|
|
89
|
+
<Text key={s.value} color={i === scopeIndex ? "yellow" : "dim"}>
|
|
90
|
+
{i === scopeIndex ? "> " : " "}
|
|
91
|
+
{s.label}
|
|
92
|
+
</Text>
|
|
93
|
+
))}
|
|
40
94
|
</Box>
|
|
41
95
|
{state.isLoading && (
|
|
42
96
|
<Box marginTop={1}>
|
|
43
|
-
<Text color="yellow"
|
|
97
|
+
<Text color="yellow">Adding marketplace...</Text>
|
|
44
98
|
</Box>
|
|
45
99
|
)}
|
|
46
100
|
<Box marginTop={1}>
|
|
47
101
|
<Text dimColor>
|
|
48
102
|
{state.isLoading
|
|
49
103
|
? "Please wait..."
|
|
50
|
-
: "
|
|
104
|
+
: "Enter to confirm, \u2191/\u2193 to navigate, Esc to go back"}
|
|
51
105
|
</Text>
|
|
52
106
|
</Box>
|
|
53
107
|
</Box>
|
|
@@ -55,6 +55,10 @@ export const MarketplaceDetail: React.FC = () => {
|
|
|
55
55
|
{marketplace.name}
|
|
56
56
|
</Text>
|
|
57
57
|
{marketplace.isBuiltin && <Text dimColor> (Built-in)</Text>}
|
|
58
|
+
{marketplace.declaredScope &&
|
|
59
|
+
marketplace.declaredScope !== "builtin" && (
|
|
60
|
+
<Text dimColor> ({marketplace.declaredScope} scope)</Text>
|
|
61
|
+
)}
|
|
58
62
|
</Box>
|
|
59
63
|
|
|
60
64
|
<Box marginBottom={1}>
|
|
@@ -39,6 +39,10 @@ export const MarketplaceList: React.FC<MarketplaceListProps> = ({
|
|
|
39
39
|
{marketplace.isBuiltin && (
|
|
40
40
|
<Text color="yellow"> [Built-in]</Text>
|
|
41
41
|
)}
|
|
42
|
+
{marketplace.declaredScope &&
|
|
43
|
+
marketplace.declaredScope !== "builtin" && (
|
|
44
|
+
<Text color="magenta"> [{marketplace.declaredScope}]</Text>
|
|
45
|
+
)}
|
|
42
46
|
</Text>
|
|
43
47
|
</Box>
|
|
44
48
|
<Box marginLeft={4} flexDirection="column">
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useReducer } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { McpServerStatus } from "wave-agent-sdk";
|
|
4
|
+
import {
|
|
5
|
+
mcpManagerReducer,
|
|
6
|
+
type McpManagerState,
|
|
7
|
+
} from "../reducers/mcpManagerReducer.js";
|
|
4
8
|
|
|
5
9
|
export interface McpManagerProps {
|
|
6
10
|
onCancel: () => void;
|
|
@@ -9,33 +13,25 @@ export interface McpManagerProps {
|
|
|
9
13
|
onDisconnectServer: (serverName: string) => Promise<boolean>;
|
|
10
14
|
}
|
|
11
15
|
|
|
16
|
+
const initialState: McpManagerState = {
|
|
17
|
+
selectedIndex: 0,
|
|
18
|
+
viewMode: "list",
|
|
19
|
+
};
|
|
20
|
+
|
|
12
21
|
export const McpManager: React.FC<McpManagerProps> = ({
|
|
13
22
|
onCancel,
|
|
14
23
|
servers,
|
|
15
24
|
onConnectServer,
|
|
16
25
|
onDisconnectServer,
|
|
17
26
|
}) => {
|
|
18
|
-
const [
|
|
19
|
-
const [viewMode, setViewMode] = useState<"list" | "detail">("list");
|
|
20
|
-
|
|
21
|
-
// Keep ref in sync with state to avoid stale closures in useInput
|
|
22
|
-
const selectedIndexRef = useRef(selectedIndex);
|
|
23
|
-
const viewModeRef = useRef(viewMode);
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
selectedIndexRef.current = selectedIndex;
|
|
27
|
-
}, [selectedIndex]);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
viewModeRef.current = viewMode;
|
|
31
|
-
}, [viewMode]);
|
|
27
|
+
const [state, dispatch] = useReducer(mcpManagerReducer, initialState);
|
|
32
28
|
|
|
33
29
|
// Dynamically calculate selectedServer based on selectedIndex and servers
|
|
34
30
|
const selectedServer =
|
|
35
|
-
viewMode === "detail" &&
|
|
31
|
+
state.viewMode === "detail" &&
|
|
36
32
|
servers.length > 0 &&
|
|
37
|
-
selectedIndex < servers.length
|
|
38
|
-
? servers[selectedIndex]
|
|
33
|
+
state.selectedIndex < servers.length
|
|
34
|
+
? servers[state.selectedIndex]
|
|
39
35
|
: null;
|
|
40
36
|
|
|
41
37
|
const formatTime = (timestamp: number): string => {
|
|
@@ -78,15 +74,15 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
78
74
|
|
|
79
75
|
useInput((input, key) => {
|
|
80
76
|
if (key.return) {
|
|
81
|
-
if (
|
|
82
|
-
|
|
77
|
+
if (state.viewMode === "list") {
|
|
78
|
+
dispatch({ type: "SET_VIEW_MODE", viewMode: "detail" });
|
|
83
79
|
}
|
|
84
80
|
return;
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
if (key.escape) {
|
|
88
|
-
if (
|
|
89
|
-
|
|
84
|
+
if (state.viewMode === "detail") {
|
|
85
|
+
dispatch({ type: "SET_VIEW_MODE", viewMode: "list" });
|
|
90
86
|
} else {
|
|
91
87
|
onCancel();
|
|
92
88
|
}
|
|
@@ -94,18 +90,18 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
94
90
|
}
|
|
95
91
|
|
|
96
92
|
if (key.upArrow) {
|
|
97
|
-
|
|
93
|
+
dispatch({ type: "MOVE_UP", serverCount: servers.length });
|
|
98
94
|
return;
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
if (key.downArrow) {
|
|
102
|
-
|
|
98
|
+
dispatch({ type: "MOVE_DOWN", serverCount: servers.length });
|
|
103
99
|
return;
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
// Hotkeys for server actions
|
|
107
103
|
if (input === "c") {
|
|
108
|
-
const server = servers[
|
|
104
|
+
const server = servers[state.selectedIndex];
|
|
109
105
|
if (
|
|
110
106
|
server &&
|
|
111
107
|
(server.status === "disconnected" || server.status === "error")
|
|
@@ -116,7 +112,7 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
116
112
|
}
|
|
117
113
|
|
|
118
114
|
if (input === "d") {
|
|
119
|
-
const server = servers[
|
|
115
|
+
const server = servers[state.selectedIndex];
|
|
120
116
|
if (server && server.status === "connected") {
|
|
121
117
|
handleDisconnect(server.name);
|
|
122
118
|
}
|
|
@@ -124,7 +120,7 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
124
120
|
}
|
|
125
121
|
});
|
|
126
122
|
|
|
127
|
-
if (viewMode === "detail" && selectedServer) {
|
|
123
|
+
if (state.viewMode === "detail" && selectedServer) {
|
|
128
124
|
return (
|
|
129
125
|
<Box
|
|
130
126
|
flexDirection="column"
|
|
@@ -273,10 +269,10 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
273
269
|
{servers.map((server, index) => (
|
|
274
270
|
<Box key={server.name} flexDirection="column">
|
|
275
271
|
<Text
|
|
276
|
-
color={index === selectedIndex ? "black" : "white"}
|
|
277
|
-
backgroundColor={index === selectedIndex ? "cyan" : undefined}
|
|
272
|
+
color={index === state.selectedIndex ? "black" : "white"}
|
|
273
|
+
backgroundColor={index === state.selectedIndex ? "cyan" : undefined}
|
|
278
274
|
>
|
|
279
|
-
{index === selectedIndex ? "▶ " : " "}
|
|
275
|
+
{index === state.selectedIndex ? "▶ " : " "}
|
|
280
276
|
{index + 1}.{" "}
|
|
281
277
|
<Text color={getStatusColor(server.status)}>
|
|
282
278
|
{getStatusIcon(server.status)}
|
|
@@ -286,7 +282,7 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
286
282
|
<Text color="green"> · {server.toolCount} tools</Text>
|
|
287
283
|
)}
|
|
288
284
|
</Text>
|
|
289
|
-
{index === selectedIndex && (
|
|
285
|
+
{index === state.selectedIndex && (
|
|
290
286
|
<Box marginLeft={4} flexDirection="column">
|
|
291
287
|
<Text color="gray" dimColor>
|
|
292
288
|
{server.config.command}
|
|
@@ -305,11 +301,11 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
305
301
|
<Box marginTop={1}>
|
|
306
302
|
<Text dimColor>
|
|
307
303
|
↑/↓ to select · Enter to view ·{" "}
|
|
308
|
-
{servers[selectedIndex]?.status === "disconnected" ||
|
|
309
|
-
servers[selectedIndex]?.status === "error"
|
|
304
|
+
{servers[state.selectedIndex]?.status === "disconnected" ||
|
|
305
|
+
servers[state.selectedIndex]?.status === "error"
|
|
310
306
|
? "c to connect · "
|
|
311
307
|
: ""}
|
|
312
|
-
{servers[selectedIndex]?.status === "connected"
|
|
308
|
+
{servers[state.selectedIndex]?.status === "connected"
|
|
313
309
|
? "d to disconnect · "
|
|
314
310
|
: ""}
|
|
315
311
|
Esc to close
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useReducer } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
+
import {
|
|
5
|
+
pluginDetailReducer,
|
|
6
|
+
type PluginDetailState,
|
|
7
|
+
} from "../reducers/pluginDetailReducer.js";
|
|
4
8
|
|
|
5
9
|
const SCOPES = [
|
|
6
10
|
{ id: "project", label: "Install for all collaborators (project scope)" },
|
|
@@ -11,20 +15,10 @@ const SCOPES = [
|
|
|
11
15
|
export const PluginDetail: React.FC = () => {
|
|
12
16
|
const { state, discoverablePlugins, installedPlugins, actions } =
|
|
13
17
|
usePluginManagerContext();
|
|
14
|
-
const [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const selectedScopeIndexRef = useRef(selectedScopeIndex);
|
|
19
|
-
const selectedActionIndexRef = useRef(selectedActionIndex);
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
selectedScopeIndexRef.current = selectedScopeIndex;
|
|
23
|
-
}, [selectedScopeIndex]);
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
selectedActionIndexRef.current = selectedActionIndex;
|
|
27
|
-
}, [selectedActionIndex]);
|
|
18
|
+
const [detailState, dispatch] = useReducer(pluginDetailReducer, {
|
|
19
|
+
selectedScopeIndex: 0,
|
|
20
|
+
selectedActionIndex: 0,
|
|
21
|
+
} as PluginDetailState);
|
|
28
22
|
|
|
29
23
|
const plugin =
|
|
30
24
|
discoverablePlugins.find(
|
|
@@ -48,22 +42,20 @@ export const PluginDetail: React.FC = () => {
|
|
|
48
42
|
);
|
|
49
43
|
actions.setView(isFromDiscover ? "DISCOVER" : "INSTALLED");
|
|
50
44
|
} else if (key.upArrow) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
45
|
+
dispatch({
|
|
46
|
+
type: "MOVE_ACTION_UP",
|
|
47
|
+
maxIndex: INSTALLED_ACTIONS.length - 1,
|
|
48
|
+
});
|
|
49
|
+
dispatch({ type: "MOVE_SCOPE_UP", maxIndex: SCOPES.length - 1 });
|
|
57
50
|
} else if (key.downArrow) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
);
|
|
51
|
+
dispatch({
|
|
52
|
+
type: "MOVE_ACTION_DOWN",
|
|
53
|
+
maxIndex: INSTALLED_ACTIONS.length - 1,
|
|
54
|
+
});
|
|
55
|
+
dispatch({ type: "MOVE_SCOPE_DOWN", maxIndex: SCOPES.length - 1 });
|
|
64
56
|
} else if (key.return && plugin && !state.isLoading) {
|
|
65
57
|
if (isInstalledAndEnabled) {
|
|
66
|
-
const action = INSTALLED_ACTIONS[
|
|
58
|
+
const action = INSTALLED_ACTIONS[detailState.selectedActionIndex].id;
|
|
67
59
|
if (action === "uninstall") {
|
|
68
60
|
actions.uninstallPlugin(plugin.name, plugin.marketplace);
|
|
69
61
|
} else {
|
|
@@ -73,7 +65,7 @@ export const PluginDetail: React.FC = () => {
|
|
|
73
65
|
actions.installPlugin(
|
|
74
66
|
plugin.name,
|
|
75
67
|
plugin.marketplace,
|
|
76
|
-
SCOPES[
|
|
68
|
+
SCOPES[detailState.selectedScopeIndex].id,
|
|
77
69
|
);
|
|
78
70
|
}
|
|
79
71
|
}
|
|
@@ -121,14 +113,14 @@ export const PluginDetail: React.FC = () => {
|
|
|
121
113
|
<Text
|
|
122
114
|
key={action.id}
|
|
123
115
|
color={
|
|
124
|
-
index === selectedActionIndex
|
|
116
|
+
index === detailState.selectedActionIndex
|
|
125
117
|
? state.isLoading
|
|
126
118
|
? "gray"
|
|
127
119
|
: "yellow"
|
|
128
120
|
: undefined
|
|
129
121
|
}
|
|
130
122
|
>
|
|
131
|
-
{index === selectedActionIndex ? "> " : " "}
|
|
123
|
+
{index === detailState.selectedActionIndex ? "> " : " "}
|
|
132
124
|
{action.label}
|
|
133
125
|
</Text>
|
|
134
126
|
))}
|
|
@@ -147,14 +139,14 @@ export const PluginDetail: React.FC = () => {
|
|
|
147
139
|
<Text
|
|
148
140
|
key={scope.id}
|
|
149
141
|
color={
|
|
150
|
-
index === selectedScopeIndex
|
|
142
|
+
index === detailState.selectedScopeIndex
|
|
151
143
|
? state.isLoading
|
|
152
144
|
? "gray"
|
|
153
145
|
: "green"
|
|
154
146
|
: undefined
|
|
155
147
|
}
|
|
156
148
|
>
|
|
157
|
-
{index === selectedScopeIndex ? "> " : " "}
|
|
149
|
+
{index === detailState.selectedScopeIndex ? "> " : " "}
|
|
158
150
|
{scope.label}
|
|
159
151
|
</Text>
|
|
160
152
|
))}
|
|
@@ -13,8 +13,9 @@ import { PluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
|
13
13
|
export const PluginManagerShell: React.FC<{
|
|
14
14
|
children?: React.ReactNode;
|
|
15
15
|
onCancel?: () => void;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
onPluginInstalled?: () => void;
|
|
17
|
+
}> = ({ children, onCancel, onPluginInstalled }) => {
|
|
18
|
+
const pluginManager = usePluginManager({ onPluginInstalled });
|
|
18
19
|
const { state, actions, discoverablePlugins } = pluginManager;
|
|
19
20
|
|
|
20
21
|
const setView = (view: ViewType) => {
|
|
@@ -33,8 +33,14 @@ export interface PluginManagerContextType {
|
|
|
33
33
|
actions: {
|
|
34
34
|
setView: (view: ViewType) => void;
|
|
35
35
|
setSelectedId: (id: string | null) => void;
|
|
36
|
-
addMarketplace: (
|
|
37
|
-
|
|
36
|
+
addMarketplace: (
|
|
37
|
+
source: string,
|
|
38
|
+
scope?: "user" | "project" | "local",
|
|
39
|
+
) => Promise<void>;
|
|
40
|
+
removeMarketplace: (
|
|
41
|
+
name: string,
|
|
42
|
+
scope?: "user" | "project" | "local",
|
|
43
|
+
) => Promise<void>;
|
|
38
44
|
updateMarketplace: (name: string) => Promise<void>;
|
|
39
45
|
installPlugin: (
|
|
40
46
|
name: string,
|