wave-code 0.2.0 → 0.4.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/dist/commands/plugin/disable.d.ts +2 -1
- package/dist/commands/plugin/disable.d.ts.map +1 -1
- package/dist/commands/plugin/disable.js +3 -2
- package/dist/commands/plugin/enable.d.ts +2 -1
- package/dist/commands/plugin/enable.d.ts.map +1 -1
- package/dist/commands/plugin/enable.js +3 -2
- package/dist/commands/plugin/install.d.ts +2 -1
- package/dist/commands/plugin/install.d.ts.map +1 -1
- package/dist/commands/plugin/list.d.ts.map +1 -1
- package/dist/commands/plugin/list.js +15 -3
- package/dist/commands/plugin/marketplace.d.ts +3 -0
- package/dist/commands/plugin/marketplace.d.ts.map +1 -1
- package/dist/commands/plugin/marketplace.js +15 -1
- package/dist/commands/plugin/uninstall.d.ts +4 -0
- package/dist/commands/plugin/uninstall.d.ts.map +1 -0
- package/dist/commands/plugin/uninstall.js +29 -0
- package/dist/commands/plugin/update.d.ts +4 -0
- package/dist/commands/plugin/update.d.ts.map +1 -0
- package/dist/commands/plugin/update.js +15 -0
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +2 -2
- package/dist/components/CommandSelector.d.ts.map +1 -1
- package/dist/components/CommandSelector.js +6 -0
- package/dist/components/Confirmation.js +1 -1
- package/dist/components/DiscoverView.d.ts +3 -0
- package/dist/components/DiscoverView.d.ts.map +1 -0
- package/dist/components/DiscoverView.js +25 -0
- package/dist/components/FileSelector.js +1 -1
- package/dist/components/HistorySearch.d.ts +8 -0
- package/dist/components/HistorySearch.d.ts.map +1 -0
- package/dist/components/HistorySearch.js +67 -0
- package/dist/components/InputBox.d.ts +1 -1
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +26 -17
- package/dist/components/InstalledView.d.ts +3 -0
- package/dist/components/InstalledView.d.ts.map +1 -0
- package/dist/components/InstalledView.js +30 -0
- package/dist/components/Markdown.d.ts.map +1 -1
- package/dist/components/Markdown.js +22 -9
- package/dist/components/MarketplaceAddForm.d.ts +3 -0
- package/dist/components/MarketplaceAddForm.d.ts.map +1 -0
- package/dist/components/MarketplaceAddForm.js +26 -0
- package/dist/components/MarketplaceDetail.d.ts +3 -0
- package/dist/components/MarketplaceDetail.d.ts.map +1 -0
- package/dist/components/MarketplaceDetail.js +38 -0
- package/dist/components/MarketplaceList.d.ts +9 -0
- package/dist/components/MarketplaceList.d.ts.map +1 -0
- package/dist/components/MarketplaceList.js +16 -0
- package/dist/components/MarketplaceView.d.ts +3 -0
- package/dist/components/MarketplaceView.d.ts.map +1 -0
- package/dist/components/MarketplaceView.js +28 -0
- package/dist/components/PluginDetail.d.ts +3 -0
- package/dist/components/PluginDetail.d.ts.map +1 -0
- package/dist/components/PluginDetail.js +63 -0
- package/dist/components/PluginList.d.ts +14 -0
- package/dist/components/PluginList.d.ts.map +1 -0
- package/dist/components/PluginList.js +12 -0
- package/dist/components/PluginManagerShell.d.ts +5 -0
- package/dist/components/PluginManagerShell.d.ts.map +1 -0
- package/dist/components/PluginManagerShell.js +89 -0
- package/dist/components/PluginManagerTypes.d.ts +33 -0
- package/dist/components/PluginManagerTypes.d.ts.map +1 -0
- package/dist/components/PluginManagerTypes.js +1 -0
- package/dist/components/RewindCommand.d.ts +9 -0
- package/dist/components/RewindCommand.d.ts.map +1 -0
- package/dist/components/RewindCommand.js +42 -0
- package/dist/components/SessionSelector.d.ts +11 -0
- package/dist/components/SessionSelector.d.ts.map +1 -0
- package/dist/components/SessionSelector.js +38 -0
- package/dist/components/SubagentBlock.d.ts.map +1 -1
- package/dist/components/SubagentBlock.js +20 -1
- package/dist/components/ToolResultDisplay.js +1 -1
- package/dist/contexts/PluginManagerContext.d.ts +4 -0
- package/dist/contexts/PluginManagerContext.d.ts.map +1 -0
- package/dist/contexts/PluginManagerContext.js +9 -0
- package/dist/contexts/useChat.d.ts +2 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +21 -0
- package/dist/hooks/useInputManager.d.ts +6 -14
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +29 -45
- package/dist/hooks/usePluginManager.d.ts +3 -0
- package/dist/hooks/usePluginManager.d.ts.map +1 -0
- package/dist/hooks/usePluginManager.js +223 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +150 -177
- package/dist/managers/InputManager.d.ts +12 -21
- package/dist/managers/InputManager.d.ts.map +1 -1
- package/dist/managers/InputManager.js +77 -108
- package/dist/plugin-manager-cli.d.ts +6 -0
- package/dist/plugin-manager-cli.d.ts.map +1 -0
- package/dist/plugin-manager-cli.js +12 -0
- package/dist/session-selector-cli.d.ts +2 -0
- package/dist/session-selector-cli.d.ts.map +1 -0
- package/dist/session-selector-cli.js +25 -0
- package/package.json +7 -3
- package/src/commands/plugin/disable.ts +7 -3
- package/src/commands/plugin/enable.ts +7 -3
- package/src/commands/plugin/install.ts +2 -1
- package/src/commands/plugin/list.ts +21 -3
- package/src/commands/plugin/marketplace.ts +17 -1
- package/src/commands/plugin/uninstall.ts +39 -0
- package/src/commands/plugin/update.ts +19 -0
- package/src/components/ChatInterface.tsx +2 -1
- package/src/components/CommandSelector.tsx +7 -0
- package/src/components/Confirmation.tsx +1 -1
- package/src/components/DiscoverView.tsx +31 -0
- package/src/components/FileSelector.tsx +1 -1
- package/src/components/HistorySearch.tsx +148 -0
- package/src/components/InputBox.tsx +43 -28
- package/src/components/InstalledView.tsx +61 -0
- package/src/components/Markdown.tsx +37 -26
- package/src/components/MarketplaceAddForm.tsx +39 -0
- package/src/components/MarketplaceDetail.tsx +79 -0
- package/src/components/MarketplaceList.tsx +52 -0
- package/src/components/MarketplaceView.tsx +43 -0
- package/src/components/PluginDetail.tsx +147 -0
- package/src/components/PluginList.tsx +51 -0
- package/src/components/PluginManagerShell.tsx +189 -0
- package/src/components/PluginManagerTypes.ts +47 -0
- package/src/components/RewindCommand.tsx +114 -0
- package/src/components/SessionSelector.tsx +127 -0
- package/src/components/SubagentBlock.tsx +29 -1
- package/src/components/ToolResultDisplay.tsx +2 -2
- package/src/contexts/PluginManagerContext.ts +15 -0
- package/src/contexts/useChat.tsx +26 -0
- package/src/hooks/useInputManager.ts +29 -61
- package/src/hooks/usePluginManager.ts +296 -0
- package/src/index.ts +241 -280
- package/src/managers/InputManager.ts +93 -149
- package/src/plugin-manager-cli.tsx +13 -0
- package/src/session-selector-cli.tsx +37 -0
- package/dist/components/BashHistorySelector.d.ts +0 -11
- package/dist/components/BashHistorySelector.d.ts.map +0 -1
- package/dist/components/BashHistorySelector.js +0 -93
- package/dist/hooks/usePagination.d.ts +0 -20
- package/dist/hooks/usePagination.d.ts.map +0 -1
- package/dist/hooks/usePagination.js +0 -168
- package/src/components/BashHistorySelector.tsx +0 -181
- package/src/hooks/usePagination.ts +0 -203
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, useInput } from "ink";
|
|
3
|
+
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
+
import { PluginList } from "./PluginList.js";
|
|
5
|
+
|
|
6
|
+
export const DiscoverView: React.FC = () => {
|
|
7
|
+
const { discoverablePlugins, actions } = usePluginManagerContext();
|
|
8
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
9
|
+
|
|
10
|
+
useInput((input, key) => {
|
|
11
|
+
if (key.upArrow) {
|
|
12
|
+
setSelectedIndex(Math.max(0, selectedIndex - 1));
|
|
13
|
+
} else if (key.downArrow) {
|
|
14
|
+
setSelectedIndex(
|
|
15
|
+
Math.min(discoverablePlugins.length - 1, selectedIndex + 1),
|
|
16
|
+
);
|
|
17
|
+
} else if (key.return) {
|
|
18
|
+
const plugin = discoverablePlugins[selectedIndex];
|
|
19
|
+
if (plugin) {
|
|
20
|
+
actions.setSelectedId(`${plugin.name}@${plugin.marketplace}`);
|
|
21
|
+
actions.setView("PLUGIN_DETAIL");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Box flexDirection="column">
|
|
28
|
+
<PluginList plugins={discoverablePlugins} selectedIndex={selectedIndex} />
|
|
29
|
+
</Box>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -131,7 +131,7 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
|
|
|
131
131
|
Use ↑↓ to navigate, Enter/Tab to select, Escape to cancel
|
|
132
132
|
</Text>
|
|
133
133
|
<Text dimColor>
|
|
134
|
-
File {selectedIndex + 1} of {files.length}
|
|
134
|
+
, File {selectedIndex + 1} of {files.length}
|
|
135
135
|
</Text>
|
|
136
136
|
</Box>
|
|
137
137
|
</Box>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { PromptHistoryManager, type PromptEntry } from "wave-agent-sdk";
|
|
4
|
+
|
|
5
|
+
export interface HistorySearchProps {
|
|
6
|
+
searchQuery: string;
|
|
7
|
+
onSelect: (prompt: string) => void;
|
|
8
|
+
onCancel: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const HistorySearch: React.FC<HistorySearchProps> = ({
|
|
12
|
+
searchQuery,
|
|
13
|
+
onSelect,
|
|
14
|
+
onCancel,
|
|
15
|
+
}) => {
|
|
16
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
17
|
+
const [entries, setEntries] = useState<PromptEntry[]>([]);
|
|
18
|
+
|
|
19
|
+
const entriesRef = React.useRef<PromptEntry[]>([]);
|
|
20
|
+
const selectedIndexRef = React.useRef(0);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
entriesRef.current = entries;
|
|
24
|
+
}, [entries]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
selectedIndexRef.current = selectedIndex;
|
|
28
|
+
}, [selectedIndex]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const fetchHistory = async () => {
|
|
32
|
+
const results = await PromptHistoryManager.searchHistory(searchQuery);
|
|
33
|
+
const limitedResults = results.slice(0, 10);
|
|
34
|
+
setEntries(limitedResults); // Limit to 10 results
|
|
35
|
+
setSelectedIndex(0);
|
|
36
|
+
};
|
|
37
|
+
fetchHistory();
|
|
38
|
+
}, [searchQuery]);
|
|
39
|
+
|
|
40
|
+
useInput((input, key) => {
|
|
41
|
+
if (key.return) {
|
|
42
|
+
if (
|
|
43
|
+
entriesRef.current.length > 0 &&
|
|
44
|
+
selectedIndexRef.current < entriesRef.current.length
|
|
45
|
+
) {
|
|
46
|
+
onSelect(entriesRef.current[selectedIndexRef.current].prompt);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (key.escape) {
|
|
52
|
+
onCancel();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (key.upArrow) {
|
|
57
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (key.downArrow) {
|
|
62
|
+
setSelectedIndex((prev) =>
|
|
63
|
+
Math.min(entriesRef.current.length - 1, prev + 1),
|
|
64
|
+
);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (entries.length === 0) {
|
|
70
|
+
return (
|
|
71
|
+
<Box
|
|
72
|
+
flexDirection="column"
|
|
73
|
+
borderStyle="single"
|
|
74
|
+
borderColor="yellow"
|
|
75
|
+
borderBottom={false}
|
|
76
|
+
borderLeft={false}
|
|
77
|
+
borderRight={false}
|
|
78
|
+
paddingTop={1}
|
|
79
|
+
>
|
|
80
|
+
<Text color="yellow">
|
|
81
|
+
No history found {searchQuery && `for "${searchQuery}"`}
|
|
82
|
+
</Text>
|
|
83
|
+
<Text dimColor>Press Escape to cancel</Text>
|
|
84
|
+
</Box>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const formatTimestamp = (timestamp: number): string => {
|
|
89
|
+
const date = new Date(timestamp);
|
|
90
|
+
const now = new Date();
|
|
91
|
+
const diffMs = now.getTime() - date.getTime();
|
|
92
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
93
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
94
|
+
|
|
95
|
+
if (diffDays > 0) {
|
|
96
|
+
return `${diffDays}d ago`;
|
|
97
|
+
} else if (diffHours > 0) {
|
|
98
|
+
return `${diffHours}h ago`;
|
|
99
|
+
} else {
|
|
100
|
+
const diffMinutes = Math.floor(diffMs / (1000 * 60));
|
|
101
|
+
return diffMinutes > 0 ? `${diffMinutes}m ago` : "just now";
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Box
|
|
107
|
+
flexDirection="column"
|
|
108
|
+
borderStyle="single"
|
|
109
|
+
borderColor="blue"
|
|
110
|
+
borderBottom={false}
|
|
111
|
+
borderLeft={false}
|
|
112
|
+
borderRight={false}
|
|
113
|
+
paddingTop={1}
|
|
114
|
+
gap={1}
|
|
115
|
+
>
|
|
116
|
+
<Box>
|
|
117
|
+
<Text color="blue" bold>
|
|
118
|
+
Prompt History {searchQuery && `(filtering: "${searchQuery}")`}
|
|
119
|
+
</Text>
|
|
120
|
+
</Box>
|
|
121
|
+
|
|
122
|
+
{entries.map((entry, index) => (
|
|
123
|
+
<Box key={index} flexDirection="column">
|
|
124
|
+
<Text
|
|
125
|
+
color={index === selectedIndex ? "black" : "white"}
|
|
126
|
+
backgroundColor={index === selectedIndex ? "blue" : undefined}
|
|
127
|
+
wrap="truncate-end"
|
|
128
|
+
>
|
|
129
|
+
{entry.prompt.replace(/\n/g, " ")}
|
|
130
|
+
</Text>
|
|
131
|
+
{index === selectedIndex && (
|
|
132
|
+
<Box marginLeft={4}>
|
|
133
|
+
<Text color="gray" dimColor>
|
|
134
|
+
{formatTimestamp(entry.timestamp)}
|
|
135
|
+
</Text>
|
|
136
|
+
</Box>
|
|
137
|
+
)}
|
|
138
|
+
</Box>
|
|
139
|
+
))}
|
|
140
|
+
|
|
141
|
+
<Box>
|
|
142
|
+
<Text dimColor>
|
|
143
|
+
Use ↑↓ to navigate, Enter to select, Escape to cancel
|
|
144
|
+
</Text>
|
|
145
|
+
</Box>
|
|
146
|
+
</Box>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import React, { useEffect
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { useInput } from "ink";
|
|
4
4
|
import { FileSelector } from "./FileSelector.js";
|
|
5
5
|
import { CommandSelector } from "./CommandSelector.js";
|
|
6
|
-
import {
|
|
6
|
+
import { HistorySearch } from "./HistorySearch.js";
|
|
7
7
|
import { MemoryTypeSelector } from "./MemoryTypeSelector.js";
|
|
8
8
|
import { BashShellManager } from "./BashShellManager.js";
|
|
9
9
|
import { McpManager } from "./McpManager.js";
|
|
10
|
+
import { RewindCommand } from "./RewindCommand.js";
|
|
10
11
|
import { useInputManager } from "../hooks/useInputManager.js";
|
|
11
12
|
import { useChat } from "../contexts/useChat.js";
|
|
12
13
|
|
|
13
14
|
import type { McpServerStatus, SlashCommand } from "wave-agent-sdk";
|
|
14
15
|
|
|
15
16
|
export const INPUT_PLACEHOLDER_TEXT =
|
|
16
|
-
"Type your message (use @ to reference files, / for commands,
|
|
17
|
+
"Type your message (use @ to reference files, / for commands, # to add memory, Ctrl+R to search history)...";
|
|
17
18
|
|
|
18
19
|
export const INPUT_PLACEHOLDER_TEXT_PREFIX = INPUT_PLACEHOLDER_TEXT.substring(
|
|
19
20
|
0,
|
|
@@ -43,7 +44,6 @@ export interface InputBoxProps {
|
|
|
43
44
|
export const InputBox: React.FC<InputBoxProps> = ({
|
|
44
45
|
isLoading = false,
|
|
45
46
|
isCommandRunning = false,
|
|
46
|
-
workdir,
|
|
47
47
|
userInputHistory = [],
|
|
48
48
|
sendMessage = () => {},
|
|
49
49
|
abortMessage = () => {},
|
|
@@ -54,12 +54,11 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
54
54
|
slashCommands = [],
|
|
55
55
|
hasSlashCommand = () => false,
|
|
56
56
|
}) => {
|
|
57
|
-
// Get current working directory - memoized to avoid repeated process.cwd() calls
|
|
58
|
-
const currentWorkdir = useMemo(() => workdir || process.cwd(), [workdir]);
|
|
59
|
-
|
|
60
57
|
const {
|
|
61
58
|
permissionMode: chatPermissionMode,
|
|
62
59
|
setPermissionMode: setChatPermissionMode,
|
|
60
|
+
handleRewindSelect,
|
|
61
|
+
messages,
|
|
63
62
|
} = useChat();
|
|
64
63
|
|
|
65
64
|
// Input manager with all input state and functionality (including images)
|
|
@@ -81,29 +80,28 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
81
80
|
handleCommandSelect,
|
|
82
81
|
handleCommandInsert,
|
|
83
82
|
handleCancelCommandSelect,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
bashHistorySearchQuery,
|
|
87
|
-
handleBashHistorySelect,
|
|
88
|
-
handleCancelBashHistorySelect,
|
|
83
|
+
handleHistorySearchSelect,
|
|
84
|
+
handleCancelHistorySearch,
|
|
89
85
|
// Memory type selector
|
|
90
86
|
showMemoryTypeSelector,
|
|
91
87
|
memoryMessage,
|
|
92
88
|
handleMemoryTypeSelect,
|
|
93
89
|
handleCancelMemoryTypeSelect,
|
|
90
|
+
// History search
|
|
91
|
+
showHistorySearch,
|
|
92
|
+
historySearchQuery,
|
|
94
93
|
// Bash/MCP Manager
|
|
95
94
|
showBashManager,
|
|
96
95
|
showMcpManager,
|
|
96
|
+
showRewindManager,
|
|
97
97
|
setShowBashManager,
|
|
98
98
|
setShowMcpManager,
|
|
99
|
+
setShowRewindManager,
|
|
99
100
|
// Permission mode
|
|
100
101
|
permissionMode,
|
|
101
102
|
setPermissionMode,
|
|
102
103
|
// Input history
|
|
103
104
|
setUserInputHistory,
|
|
104
|
-
// Complex handlers combining multiple operations
|
|
105
|
-
handleBashHistoryExecuteAndSend,
|
|
106
|
-
handleBashHistoryDelete,
|
|
107
105
|
// Main handler
|
|
108
106
|
handleInput,
|
|
109
107
|
// Manager ready state
|
|
@@ -138,9 +136,11 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
138
136
|
);
|
|
139
137
|
});
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
const handleRewindCancel = () => {
|
|
140
|
+
if (setShowRewindManager) {
|
|
141
|
+
setShowRewindManager(false);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
144
|
|
|
145
145
|
const isPlaceholder = !inputText;
|
|
146
146
|
const placeholderText = INPUT_PLACEHOLDER_TEXT;
|
|
@@ -154,7 +154,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
154
154
|
cursorPosition < displayText.length ? displayText[cursorPosition] : " ";
|
|
155
155
|
const afterCursor = displayText.substring(cursorPosition + 1);
|
|
156
156
|
|
|
157
|
-
// Always show cursor, allow user to continue input during
|
|
157
|
+
// Always show cursor, allow user to continue input during memory mode
|
|
158
158
|
const shouldShowCursor = true;
|
|
159
159
|
|
|
160
160
|
// Only show the Box after InputManager is created on first mount
|
|
@@ -162,6 +162,23 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
162
162
|
return null;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
const handleRewindSelectWithClose = async (index: number) => {
|
|
166
|
+
if (setShowRewindManager) {
|
|
167
|
+
setShowRewindManager(false);
|
|
168
|
+
}
|
|
169
|
+
await handleRewindSelect(index);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (showRewindManager) {
|
|
173
|
+
return (
|
|
174
|
+
<RewindCommand
|
|
175
|
+
messages={messages}
|
|
176
|
+
onSelect={handleRewindSelectWithClose}
|
|
177
|
+
onCancel={handleRewindCancel}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
165
182
|
return (
|
|
166
183
|
<Box flexDirection="column">
|
|
167
184
|
{showFileSelector && (
|
|
@@ -183,14 +200,11 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
183
200
|
/>
|
|
184
201
|
)}
|
|
185
202
|
|
|
186
|
-
{
|
|
187
|
-
<
|
|
188
|
-
searchQuery={
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
onExecute={handleBashHistoryExecuteAndSend}
|
|
192
|
-
onDelete={handleBashHistoryDelete}
|
|
193
|
-
onCancel={handleCancelBashHistorySelect}
|
|
203
|
+
{showHistorySearch && (
|
|
204
|
+
<HistorySearch
|
|
205
|
+
searchQuery={historySearchQuery}
|
|
206
|
+
onSelect={handleHistorySearchSelect}
|
|
207
|
+
onCancel={handleCancelHistorySearch}
|
|
194
208
|
/>
|
|
195
209
|
)}
|
|
196
210
|
|
|
@@ -214,7 +228,8 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
214
228
|
onDisconnectServer={disconnectMcpServer}
|
|
215
229
|
/>
|
|
216
230
|
)}
|
|
217
|
-
|
|
231
|
+
|
|
232
|
+
{showBashManager || showMcpManager || showRewindManager || (
|
|
218
233
|
<Box flexDirection="column">
|
|
219
234
|
<Box
|
|
220
235
|
borderStyle="single"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
+
|
|
5
|
+
export const InstalledView: React.FC = () => {
|
|
6
|
+
const { installedPlugins, actions } = usePluginManagerContext();
|
|
7
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
8
|
+
|
|
9
|
+
useInput((input, key) => {
|
|
10
|
+
if (key.upArrow) {
|
|
11
|
+
setSelectedIndex(Math.max(0, selectedIndex - 1));
|
|
12
|
+
} else if (key.downArrow) {
|
|
13
|
+
setSelectedIndex(
|
|
14
|
+
Math.min(installedPlugins.length - 1, selectedIndex + 1),
|
|
15
|
+
);
|
|
16
|
+
} else if (key.return) {
|
|
17
|
+
const plugin = installedPlugins[selectedIndex];
|
|
18
|
+
if (plugin) {
|
|
19
|
+
actions.setSelectedId(`${plugin.name}@${plugin.marketplace}`);
|
|
20
|
+
actions.setView("PLUGIN_DETAIL");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (installedPlugins.length === 0) {
|
|
26
|
+
return (
|
|
27
|
+
<Box padding={1}>
|
|
28
|
+
<Text dimColor>No plugins installed.</Text>
|
|
29
|
+
</Box>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Box flexDirection="column">
|
|
35
|
+
{installedPlugins.map((plugin, index) => {
|
|
36
|
+
const isSelected = index === selectedIndex;
|
|
37
|
+
return (
|
|
38
|
+
<Box
|
|
39
|
+
key={`${plugin.name}@${plugin.marketplace}`}
|
|
40
|
+
flexDirection="column"
|
|
41
|
+
marginBottom={1}
|
|
42
|
+
>
|
|
43
|
+
<Box>
|
|
44
|
+
<Text color={isSelected ? "cyan" : undefined}>
|
|
45
|
+
{isSelected ? "> " : " "}
|
|
46
|
+
<Text bold>{plugin.name}</Text>
|
|
47
|
+
<Text dimColor> @{plugin.marketplace}</Text>
|
|
48
|
+
{plugin.scope && <Text color="gray"> ({plugin.scope})</Text>}
|
|
49
|
+
</Text>
|
|
50
|
+
</Box>
|
|
51
|
+
{isSelected && (
|
|
52
|
+
<Box marginLeft={4}>
|
|
53
|
+
<Text dimColor>Press Enter for actions</Text>
|
|
54
|
+
</Box>
|
|
55
|
+
)}
|
|
56
|
+
</Box>
|
|
57
|
+
);
|
|
58
|
+
})}
|
|
59
|
+
</Box>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { Box, Text, useStdout } from "ink";
|
|
3
3
|
import { marked, type Token, type Tokens } from "marked";
|
|
4
|
+
import { highlight } from "cli-highlight";
|
|
4
5
|
|
|
5
6
|
export interface MarkdownProps {
|
|
6
7
|
children: string;
|
|
@@ -12,7 +13,8 @@ const unescapeHtml = (html: string) => {
|
|
|
12
13
|
.replace(/</g, "<")
|
|
13
14
|
.replace(/>/g, ">")
|
|
14
15
|
.replace(/"/g, '"')
|
|
15
|
-
.replace(/'/g, "'")
|
|
16
|
+
.replace(/'/g, "'")
|
|
17
|
+
.replace(/'/g, "'");
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
const InlineRenderer = ({ tokens }: { tokens: Token[] }) => {
|
|
@@ -53,16 +55,21 @@ const InlineRenderer = ({ tokens }: { tokens: Token[] }) => {
|
|
|
53
55
|
{unescapeHtml((token as Tokens.Codespan).text)}
|
|
54
56
|
</Text>
|
|
55
57
|
);
|
|
56
|
-
case "link":
|
|
58
|
+
case "link": {
|
|
59
|
+
const t = token as Tokens.Link;
|
|
57
60
|
return (
|
|
58
|
-
<Text key={index}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
<Text key={index}>
|
|
62
|
+
<Text color="blue" underline>
|
|
63
|
+
{t.tokens ? (
|
|
64
|
+
<InlineRenderer tokens={t.tokens} />
|
|
65
|
+
) : (
|
|
66
|
+
unescapeHtml(t.text)
|
|
67
|
+
)}
|
|
68
|
+
</Text>
|
|
69
|
+
<Text color="gray"> ({t.href})</Text>
|
|
64
70
|
</Text>
|
|
65
71
|
);
|
|
72
|
+
}
|
|
66
73
|
case "br":
|
|
67
74
|
return <Text key={index}>{"\n"}</Text>;
|
|
68
75
|
case "del":
|
|
@@ -199,23 +206,29 @@ const BlockRenderer = ({ tokens }: { tokens: Token[] }) => {
|
|
|
199
206
|
case "paragraph": {
|
|
200
207
|
const t = token as Tokens.Paragraph;
|
|
201
208
|
return (
|
|
202
|
-
<Box
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
flexWrap="wrap"
|
|
207
|
-
>
|
|
208
|
-
<InlineRenderer tokens={t.tokens} />
|
|
209
|
+
<Box key={index} marginBottom={1} flexDirection="row">
|
|
210
|
+
<Text>
|
|
211
|
+
<InlineRenderer tokens={t.tokens} />
|
|
212
|
+
</Text>
|
|
209
213
|
</Box>
|
|
210
214
|
);
|
|
211
215
|
}
|
|
212
216
|
case "code": {
|
|
213
217
|
const t = token as Tokens.Code;
|
|
214
218
|
if (t.lang !== undefined) {
|
|
215
|
-
const
|
|
219
|
+
const raw = token.raw.endsWith("\n")
|
|
220
|
+
? token.raw.slice(0, -1)
|
|
221
|
+
: token.raw;
|
|
222
|
+
const lines = raw.split("\n");
|
|
216
223
|
const opening = lines[0];
|
|
217
224
|
const closing = lines[lines.length - 1];
|
|
218
225
|
const content = lines.slice(1, -1).join("\n");
|
|
226
|
+
const highlighted = content
|
|
227
|
+
? highlight(unescapeHtml(content), {
|
|
228
|
+
language: t.lang,
|
|
229
|
+
ignoreIllegals: true,
|
|
230
|
+
})
|
|
231
|
+
: "";
|
|
219
232
|
return (
|
|
220
233
|
<Box
|
|
221
234
|
key={index}
|
|
@@ -224,7 +237,7 @@ const BlockRenderer = ({ tokens }: { tokens: Token[] }) => {
|
|
|
224
237
|
marginBottom={1}
|
|
225
238
|
>
|
|
226
239
|
<Text color="gray">{opening}</Text>
|
|
227
|
-
{
|
|
240
|
+
{highlighted && <Text>{highlighted}</Text>}
|
|
228
241
|
<Text color="gray">{closing}</Text>
|
|
229
242
|
</Box>
|
|
230
243
|
);
|
|
@@ -236,7 +249,7 @@ const BlockRenderer = ({ tokens }: { tokens: Token[] }) => {
|
|
|
236
249
|
paddingX={1}
|
|
237
250
|
marginBottom={1}
|
|
238
251
|
>
|
|
239
|
-
<Text>{t.text}</Text>
|
|
252
|
+
<Text>{unescapeHtml(t.text)}</Text>
|
|
240
253
|
</Box>
|
|
241
254
|
);
|
|
242
255
|
}
|
|
@@ -261,14 +274,12 @@ const BlockRenderer = ({ tokens }: { tokens: Token[] }) => {
|
|
|
261
274
|
if (itemToken.type === "text") {
|
|
262
275
|
const it = itemToken as Tokens.Text;
|
|
263
276
|
return (
|
|
264
|
-
<Box
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
tokens={it.tokens || [itemToken]}
|
|
271
|
-
/>
|
|
277
|
+
<Box key={itemIndex} flexDirection="row">
|
|
278
|
+
<Text>
|
|
279
|
+
<InlineRenderer
|
|
280
|
+
tokens={it.tokens || [itemToken]}
|
|
281
|
+
/>
|
|
282
|
+
</Text>
|
|
272
283
|
</Box>
|
|
273
284
|
);
|
|
274
285
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
+
|
|
5
|
+
export const MarketplaceAddForm: React.FC = () => {
|
|
6
|
+
const { actions } = usePluginManagerContext();
|
|
7
|
+
const [source, setSource] = useState("");
|
|
8
|
+
|
|
9
|
+
useInput((input, key) => {
|
|
10
|
+
if (key.escape) {
|
|
11
|
+
actions.setView("MARKETPLACES");
|
|
12
|
+
} else if (key.return) {
|
|
13
|
+
if (source.trim()) {
|
|
14
|
+
actions.addMarketplace(source.trim());
|
|
15
|
+
actions.setView("MARKETPLACES");
|
|
16
|
+
}
|
|
17
|
+
} else if (key.backspace || key.delete) {
|
|
18
|
+
setSource((prev) => prev.slice(0, -1));
|
|
19
|
+
} else if (input.length === 1) {
|
|
20
|
+
setSource((prev) => prev + input);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Box flexDirection="column" padding={1}>
|
|
26
|
+
<Text bold color="cyan">
|
|
27
|
+
Add Marketplace
|
|
28
|
+
</Text>
|
|
29
|
+
<Box marginTop={1}>
|
|
30
|
+
<Text>Source (URL or Path): </Text>
|
|
31
|
+
<Text color="yellow">{source}</Text>
|
|
32
|
+
<Text color="yellow">_</Text>
|
|
33
|
+
</Box>
|
|
34
|
+
<Box marginTop={1}>
|
|
35
|
+
<Text dimColor>Press Enter to add, Esc to cancel</Text>
|
|
36
|
+
</Box>
|
|
37
|
+
</Box>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
+
|
|
5
|
+
export const MarketplaceDetail: React.FC = () => {
|
|
6
|
+
const { state, marketplaces, actions } = usePluginManagerContext();
|
|
7
|
+
const [selectedActionIndex, setSelectedActionIndex] = useState(0);
|
|
8
|
+
|
|
9
|
+
const marketplace = marketplaces.find((m) => m.name === state.selectedId);
|
|
10
|
+
|
|
11
|
+
const ACTIONS = [
|
|
12
|
+
{ id: "update", label: "Update marketplace" },
|
|
13
|
+
{ id: "remove", label: "Remove marketplace" },
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
useInput((input, key) => {
|
|
17
|
+
if (key.escape) {
|
|
18
|
+
actions.setView("MARKETPLACES");
|
|
19
|
+
} else if (key.upArrow) {
|
|
20
|
+
setSelectedActionIndex((prev) =>
|
|
21
|
+
prev > 0 ? prev - 1 : ACTIONS.length - 1,
|
|
22
|
+
);
|
|
23
|
+
} else if (key.downArrow) {
|
|
24
|
+
setSelectedActionIndex((prev) =>
|
|
25
|
+
prev < ACTIONS.length - 1 ? prev + 1 : 0,
|
|
26
|
+
);
|
|
27
|
+
} else if (key.return && marketplace) {
|
|
28
|
+
const action = ACTIONS[selectedActionIndex].id;
|
|
29
|
+
if (action === "update") {
|
|
30
|
+
actions.updateMarketplace(marketplace.name);
|
|
31
|
+
} else {
|
|
32
|
+
actions.removeMarketplace(marketplace.name);
|
|
33
|
+
}
|
|
34
|
+
actions.setView("MARKETPLACES");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!marketplace) {
|
|
39
|
+
return (
|
|
40
|
+
<Box>
|
|
41
|
+
<Text color="red">Marketplace not found.</Text>
|
|
42
|
+
</Box>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Box flexDirection="column" padding={1}>
|
|
48
|
+
<Box marginBottom={1}>
|
|
49
|
+
<Text bold color="cyan">
|
|
50
|
+
{marketplace.name}
|
|
51
|
+
</Text>
|
|
52
|
+
{marketplace.isBuiltin && <Text dimColor> (Built-in)</Text>}
|
|
53
|
+
</Box>
|
|
54
|
+
|
|
55
|
+
<Box marginBottom={1}>
|
|
56
|
+
<Text>Source: {JSON.stringify(marketplace.source)}</Text>
|
|
57
|
+
</Box>
|
|
58
|
+
|
|
59
|
+
<Box marginTop={1} flexDirection="column">
|
|
60
|
+
<Text bold>Marketplace Actions:</Text>
|
|
61
|
+
{ACTIONS.map((action, index) => (
|
|
62
|
+
<Text
|
|
63
|
+
key={action.id}
|
|
64
|
+
color={index === selectedActionIndex ? "yellow" : undefined}
|
|
65
|
+
>
|
|
66
|
+
{index === selectedActionIndex ? "> " : " "}
|
|
67
|
+
{action.label}
|
|
68
|
+
</Text>
|
|
69
|
+
))}
|
|
70
|
+
<Box marginTop={1}>
|
|
71
|
+
<Text dimColor>Use ↑/↓ to select, Enter to confirm</Text>
|
|
72
|
+
</Box>
|
|
73
|
+
<Box marginTop={1}>
|
|
74
|
+
<Text dimColor>Press Esc to go back</Text>
|
|
75
|
+
</Box>
|
|
76
|
+
</Box>
|
|
77
|
+
</Box>
|
|
78
|
+
);
|
|
79
|
+
};
|