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.
Files changed (140) hide show
  1. package/dist/commands/plugin/disable.d.ts +2 -1
  2. package/dist/commands/plugin/disable.d.ts.map +1 -1
  3. package/dist/commands/plugin/disable.js +3 -2
  4. package/dist/commands/plugin/enable.d.ts +2 -1
  5. package/dist/commands/plugin/enable.d.ts.map +1 -1
  6. package/dist/commands/plugin/enable.js +3 -2
  7. package/dist/commands/plugin/install.d.ts +2 -1
  8. package/dist/commands/plugin/install.d.ts.map +1 -1
  9. package/dist/commands/plugin/list.d.ts.map +1 -1
  10. package/dist/commands/plugin/list.js +15 -3
  11. package/dist/commands/plugin/marketplace.d.ts +3 -0
  12. package/dist/commands/plugin/marketplace.d.ts.map +1 -1
  13. package/dist/commands/plugin/marketplace.js +15 -1
  14. package/dist/commands/plugin/uninstall.d.ts +4 -0
  15. package/dist/commands/plugin/uninstall.d.ts.map +1 -0
  16. package/dist/commands/plugin/uninstall.js +29 -0
  17. package/dist/commands/plugin/update.d.ts +4 -0
  18. package/dist/commands/plugin/update.d.ts.map +1 -0
  19. package/dist/commands/plugin/update.js +15 -0
  20. package/dist/components/ChatInterface.d.ts.map +1 -1
  21. package/dist/components/ChatInterface.js +2 -2
  22. package/dist/components/CommandSelector.d.ts.map +1 -1
  23. package/dist/components/CommandSelector.js +6 -0
  24. package/dist/components/Confirmation.js +1 -1
  25. package/dist/components/DiscoverView.d.ts +3 -0
  26. package/dist/components/DiscoverView.d.ts.map +1 -0
  27. package/dist/components/DiscoverView.js +25 -0
  28. package/dist/components/FileSelector.js +1 -1
  29. package/dist/components/HistorySearch.d.ts +8 -0
  30. package/dist/components/HistorySearch.d.ts.map +1 -0
  31. package/dist/components/HistorySearch.js +67 -0
  32. package/dist/components/InputBox.d.ts +1 -1
  33. package/dist/components/InputBox.d.ts.map +1 -1
  34. package/dist/components/InputBox.js +26 -17
  35. package/dist/components/InstalledView.d.ts +3 -0
  36. package/dist/components/InstalledView.d.ts.map +1 -0
  37. package/dist/components/InstalledView.js +30 -0
  38. package/dist/components/Markdown.d.ts.map +1 -1
  39. package/dist/components/Markdown.js +22 -9
  40. package/dist/components/MarketplaceAddForm.d.ts +3 -0
  41. package/dist/components/MarketplaceAddForm.d.ts.map +1 -0
  42. package/dist/components/MarketplaceAddForm.js +26 -0
  43. package/dist/components/MarketplaceDetail.d.ts +3 -0
  44. package/dist/components/MarketplaceDetail.d.ts.map +1 -0
  45. package/dist/components/MarketplaceDetail.js +38 -0
  46. package/dist/components/MarketplaceList.d.ts +9 -0
  47. package/dist/components/MarketplaceList.d.ts.map +1 -0
  48. package/dist/components/MarketplaceList.js +16 -0
  49. package/dist/components/MarketplaceView.d.ts +3 -0
  50. package/dist/components/MarketplaceView.d.ts.map +1 -0
  51. package/dist/components/MarketplaceView.js +28 -0
  52. package/dist/components/PluginDetail.d.ts +3 -0
  53. package/dist/components/PluginDetail.d.ts.map +1 -0
  54. package/dist/components/PluginDetail.js +63 -0
  55. package/dist/components/PluginList.d.ts +14 -0
  56. package/dist/components/PluginList.d.ts.map +1 -0
  57. package/dist/components/PluginList.js +12 -0
  58. package/dist/components/PluginManagerShell.d.ts +5 -0
  59. package/dist/components/PluginManagerShell.d.ts.map +1 -0
  60. package/dist/components/PluginManagerShell.js +89 -0
  61. package/dist/components/PluginManagerTypes.d.ts +33 -0
  62. package/dist/components/PluginManagerTypes.d.ts.map +1 -0
  63. package/dist/components/PluginManagerTypes.js +1 -0
  64. package/dist/components/RewindCommand.d.ts +9 -0
  65. package/dist/components/RewindCommand.d.ts.map +1 -0
  66. package/dist/components/RewindCommand.js +42 -0
  67. package/dist/components/SessionSelector.d.ts +11 -0
  68. package/dist/components/SessionSelector.d.ts.map +1 -0
  69. package/dist/components/SessionSelector.js +38 -0
  70. package/dist/components/SubagentBlock.d.ts.map +1 -1
  71. package/dist/components/SubagentBlock.js +20 -1
  72. package/dist/components/ToolResultDisplay.js +1 -1
  73. package/dist/contexts/PluginManagerContext.d.ts +4 -0
  74. package/dist/contexts/PluginManagerContext.d.ts.map +1 -0
  75. package/dist/contexts/PluginManagerContext.js +9 -0
  76. package/dist/contexts/useChat.d.ts +2 -0
  77. package/dist/contexts/useChat.d.ts.map +1 -1
  78. package/dist/contexts/useChat.js +21 -0
  79. package/dist/hooks/useInputManager.d.ts +6 -14
  80. package/dist/hooks/useInputManager.d.ts.map +1 -1
  81. package/dist/hooks/useInputManager.js +29 -45
  82. package/dist/hooks/usePluginManager.d.ts +3 -0
  83. package/dist/hooks/usePluginManager.d.ts.map +1 -0
  84. package/dist/hooks/usePluginManager.js +223 -0
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +150 -177
  87. package/dist/managers/InputManager.d.ts +12 -21
  88. package/dist/managers/InputManager.d.ts.map +1 -1
  89. package/dist/managers/InputManager.js +77 -108
  90. package/dist/plugin-manager-cli.d.ts +6 -0
  91. package/dist/plugin-manager-cli.d.ts.map +1 -0
  92. package/dist/plugin-manager-cli.js +12 -0
  93. package/dist/session-selector-cli.d.ts +2 -0
  94. package/dist/session-selector-cli.d.ts.map +1 -0
  95. package/dist/session-selector-cli.js +25 -0
  96. package/package.json +7 -3
  97. package/src/commands/plugin/disable.ts +7 -3
  98. package/src/commands/plugin/enable.ts +7 -3
  99. package/src/commands/plugin/install.ts +2 -1
  100. package/src/commands/plugin/list.ts +21 -3
  101. package/src/commands/plugin/marketplace.ts +17 -1
  102. package/src/commands/plugin/uninstall.ts +39 -0
  103. package/src/commands/plugin/update.ts +19 -0
  104. package/src/components/ChatInterface.tsx +2 -1
  105. package/src/components/CommandSelector.tsx +7 -0
  106. package/src/components/Confirmation.tsx +1 -1
  107. package/src/components/DiscoverView.tsx +31 -0
  108. package/src/components/FileSelector.tsx +1 -1
  109. package/src/components/HistorySearch.tsx +148 -0
  110. package/src/components/InputBox.tsx +43 -28
  111. package/src/components/InstalledView.tsx +61 -0
  112. package/src/components/Markdown.tsx +37 -26
  113. package/src/components/MarketplaceAddForm.tsx +39 -0
  114. package/src/components/MarketplaceDetail.tsx +79 -0
  115. package/src/components/MarketplaceList.tsx +52 -0
  116. package/src/components/MarketplaceView.tsx +43 -0
  117. package/src/components/PluginDetail.tsx +147 -0
  118. package/src/components/PluginList.tsx +51 -0
  119. package/src/components/PluginManagerShell.tsx +189 -0
  120. package/src/components/PluginManagerTypes.ts +47 -0
  121. package/src/components/RewindCommand.tsx +114 -0
  122. package/src/components/SessionSelector.tsx +127 -0
  123. package/src/components/SubagentBlock.tsx +29 -1
  124. package/src/components/ToolResultDisplay.tsx +2 -2
  125. package/src/contexts/PluginManagerContext.ts +15 -0
  126. package/src/contexts/useChat.tsx +26 -0
  127. package/src/hooks/useInputManager.ts +29 -61
  128. package/src/hooks/usePluginManager.ts +296 -0
  129. package/src/index.ts +241 -280
  130. package/src/managers/InputManager.ts +93 -149
  131. package/src/plugin-manager-cli.tsx +13 -0
  132. package/src/session-selector-cli.tsx +37 -0
  133. package/dist/components/BashHistorySelector.d.ts +0 -11
  134. package/dist/components/BashHistorySelector.d.ts.map +0 -1
  135. package/dist/components/BashHistorySelector.js +0 -93
  136. package/dist/hooks/usePagination.d.ts +0 -20
  137. package/dist/hooks/usePagination.d.ts.map +0 -1
  138. package/dist/hooks/usePagination.js +0 -168
  139. package/src/components/BashHistorySelector.tsx +0 -181
  140. package/src/hooks/usePagination.ts +0 -203
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+ import { KnownMarketplace } from "wave-agent-sdk";
4
+
5
+ interface MarketplaceListProps {
6
+ marketplaces: KnownMarketplace[];
7
+ selectedIndex: number;
8
+ }
9
+
10
+ export const MarketplaceList: React.FC<MarketplaceListProps> = ({
11
+ marketplaces,
12
+ selectedIndex,
13
+ }) => {
14
+ if (marketplaces.length === 0) {
15
+ return (
16
+ <Box padding={1}>
17
+ <Text dimColor>No marketplaces registered.</Text>
18
+ </Box>
19
+ );
20
+ }
21
+
22
+ return (
23
+ <Box flexDirection="column">
24
+ {marketplaces.map((marketplace, index) => {
25
+ const isSelected = index === selectedIndex;
26
+ const sourceStr =
27
+ marketplace.source.source === "directory"
28
+ ? marketplace.source.path
29
+ : marketplace.source.source === "github"
30
+ ? marketplace.source.repo
31
+ : marketplace.source.url;
32
+
33
+ return (
34
+ <Box key={marketplace.name} flexDirection="column" marginBottom={1}>
35
+ <Box>
36
+ <Text color={isSelected ? "cyan" : undefined}>
37
+ {isSelected ? "> " : " "}
38
+ <Text bold>{marketplace.name}</Text>
39
+ {marketplace.isBuiltin && (
40
+ <Text color="yellow"> [Built-in]</Text>
41
+ )}
42
+ </Text>
43
+ </Box>
44
+ <Box marginLeft={4}>
45
+ <Text dimColor>Source: {sourceStr}</Text>
46
+ </Box>
47
+ </Box>
48
+ );
49
+ })}
50
+ </Box>
51
+ );
52
+ };
@@ -0,0 +1,43 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
4
+
5
+ import { MarketplaceList } from "./MarketplaceList.js";
6
+
7
+ export const MarketplaceView: React.FC = () => {
8
+ const { marketplaces, actions } = usePluginManagerContext();
9
+ const [selectedIndex, setSelectedIndex] = useState(0);
10
+
11
+ useInput((input, key) => {
12
+ if (key.upArrow) {
13
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
14
+ } else if (key.downArrow) {
15
+ setSelectedIndex(Math.min(marketplaces.length - 1, selectedIndex + 1));
16
+ } else if (key.return) {
17
+ const mk = marketplaces[selectedIndex];
18
+ if (mk) {
19
+ actions.setSelectedId(mk.name);
20
+ actions.setView("MARKETPLACE_DETAIL");
21
+ }
22
+ } else if (input === "a") {
23
+ actions.setView("ADD_MARKETPLACE");
24
+ }
25
+ });
26
+
27
+ return (
28
+ <Box flexDirection="column">
29
+ <Box marginBottom={1}>
30
+ <Text color="green">Press 'a' to add a new marketplace</Text>
31
+ </Box>
32
+ <MarketplaceList
33
+ marketplaces={marketplaces}
34
+ selectedIndex={selectedIndex}
35
+ />
36
+ {marketplaces.length > 0 && (
37
+ <Box marginLeft={4} marginTop={1}>
38
+ <Text dimColor>Press Enter for actions</Text>
39
+ </Box>
40
+ )}
41
+ </Box>
42
+ );
43
+ };
@@ -0,0 +1,147 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
4
+
5
+ const SCOPES = [
6
+ { id: "project", label: "Install for all collaborators (project scope)" },
7
+ { id: "user", label: "Install for you (user scope)" },
8
+ { id: "local", label: "Install for you, in this repo only (local scope)" },
9
+ ] as const;
10
+
11
+ export const PluginDetail: React.FC = () => {
12
+ const { state, discoverablePlugins, installedPlugins, actions } =
13
+ usePluginManagerContext();
14
+ const [selectedScopeIndex, setSelectedScopeIndex] = useState(0);
15
+ const [selectedActionIndex, setSelectedActionIndex] = useState(0);
16
+
17
+ const plugin =
18
+ discoverablePlugins.find(
19
+ (p) => `${p.name}@${p.marketplace}` === state.selectedId,
20
+ ) ||
21
+ installedPlugins.find(
22
+ (p) => `${p.name}@${p.marketplace}` === state.selectedId,
23
+ );
24
+
25
+ const INSTALLED_ACTIONS = [
26
+ { id: "uninstall", label: "Uninstall plugin" },
27
+ { id: "update", label: "Update plugin (reinstall)" },
28
+ ] as const;
29
+
30
+ const isInstalledAndEnabled = plugin && "enabled" in plugin && plugin.enabled;
31
+
32
+ useInput((input, key) => {
33
+ if (key.escape) {
34
+ const isFromDiscover = discoverablePlugins.find(
35
+ (p) => `${p.name}@${p.marketplace}` === state.selectedId,
36
+ );
37
+ actions.setView(isFromDiscover ? "DISCOVER" : "INSTALLED");
38
+ } else if (key.upArrow) {
39
+ if (isInstalledAndEnabled) {
40
+ setSelectedActionIndex((prev) =>
41
+ prev > 0 ? prev - 1 : INSTALLED_ACTIONS.length - 1,
42
+ );
43
+ } else {
44
+ setSelectedScopeIndex((prev) =>
45
+ prev > 0 ? prev - 1 : SCOPES.length - 1,
46
+ );
47
+ }
48
+ } else if (key.downArrow) {
49
+ if (isInstalledAndEnabled) {
50
+ setSelectedActionIndex((prev) =>
51
+ prev < INSTALLED_ACTIONS.length - 1 ? prev + 1 : 0,
52
+ );
53
+ } else {
54
+ setSelectedScopeIndex((prev) =>
55
+ prev < SCOPES.length - 1 ? prev + 1 : 0,
56
+ );
57
+ }
58
+ } else if (key.return && plugin) {
59
+ if (isInstalledAndEnabled) {
60
+ const action = INSTALLED_ACTIONS[selectedActionIndex].id;
61
+ if (action === "uninstall") {
62
+ actions.uninstallPlugin(plugin.name, plugin.marketplace);
63
+ } else {
64
+ actions.updatePlugin(plugin.name, plugin.marketplace);
65
+ }
66
+ actions.setView("INSTALLED");
67
+ } else {
68
+ actions.installPlugin(
69
+ plugin.name,
70
+ plugin.marketplace,
71
+ SCOPES[selectedScopeIndex].id,
72
+ );
73
+ actions.setView("INSTALLED");
74
+ }
75
+ }
76
+ });
77
+
78
+ if (!plugin) {
79
+ return (
80
+ <Box>
81
+ <Text color="red">Plugin not found.</Text>
82
+ </Box>
83
+ );
84
+ }
85
+
86
+ return (
87
+ <Box flexDirection="column" padding={1}>
88
+ <Box marginBottom={1}>
89
+ <Text bold color="cyan">
90
+ {plugin.name}
91
+ </Text>
92
+ <Text dimColor> @{plugin.marketplace}</Text>
93
+ </Box>
94
+
95
+ <Box marginBottom={1}>
96
+ <Text>{"description" in plugin ? plugin.description : ""}</Text>
97
+ </Box>
98
+
99
+ {plugin.version && (
100
+ <Box marginBottom={1}>
101
+ <Text>
102
+ Version: <Text color="blue">{plugin.version}</Text>
103
+ </Text>
104
+ </Box>
105
+ )}
106
+
107
+ <Box marginTop={1} flexDirection="column">
108
+ {isInstalledAndEnabled ? (
109
+ <Box flexDirection="column">
110
+ <Text bold>Plugin Actions:</Text>
111
+ {INSTALLED_ACTIONS.map((action, index) => (
112
+ <Text
113
+ key={action.id}
114
+ color={index === selectedActionIndex ? "yellow" : undefined}
115
+ >
116
+ {index === selectedActionIndex ? "> " : " "}
117
+ {action.label}
118
+ </Text>
119
+ ))}
120
+ <Box marginTop={1}>
121
+ <Text dimColor>Use ↑/↓ to select, Enter to confirm</Text>
122
+ </Box>
123
+ </Box>
124
+ ) : (
125
+ <Box flexDirection="column">
126
+ <Text bold>Select Installation Scope:</Text>
127
+ {SCOPES.map((scope, index) => (
128
+ <Text
129
+ key={scope.id}
130
+ color={index === selectedScopeIndex ? "green" : undefined}
131
+ >
132
+ {index === selectedScopeIndex ? "> " : " "}
133
+ {scope.label}
134
+ </Text>
135
+ ))}
136
+ <Box marginTop={1}>
137
+ <Text dimColor>Use ↑/↓ to select, Enter to install</Text>
138
+ </Box>
139
+ </Box>
140
+ )}
141
+ <Box marginTop={1}>
142
+ <Text dimColor>Press Esc to go back</Text>
143
+ </Box>
144
+ </Box>
145
+ </Box>
146
+ );
147
+ };
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+ import { MarketplacePluginEntry } from "wave-agent-sdk";
4
+
5
+ interface PluginListProps {
6
+ plugins: (MarketplacePluginEntry & {
7
+ marketplace: string;
8
+ installed: boolean;
9
+ version?: string;
10
+ })[];
11
+ selectedIndex: number;
12
+ onSelect?: (index: number) => void;
13
+ }
14
+
15
+ export const PluginList: React.FC<PluginListProps> = ({
16
+ plugins,
17
+ selectedIndex,
18
+ }) => {
19
+ if (plugins.length === 0) {
20
+ return (
21
+ <Box padding={1}>
22
+ <Text dimColor>No plugins found.</Text>
23
+ </Box>
24
+ );
25
+ }
26
+
27
+ return (
28
+ <Box flexDirection="column">
29
+ {plugins.map((plugin, index) => {
30
+ const isSelected = index === selectedIndex;
31
+ const pluginId = `${plugin.name}@${plugin.marketplace}`;
32
+
33
+ return (
34
+ <Box key={pluginId} flexDirection="column" marginBottom={1}>
35
+ <Box>
36
+ <Text color={isSelected ? "cyan" : undefined}>
37
+ {isSelected ? "> " : " "}
38
+ <Text bold>{plugin.name}</Text>
39
+ <Text dimColor> @{plugin.marketplace}</Text>
40
+ {plugin.version && <Text color="blue"> v{plugin.version}</Text>}
41
+ </Text>
42
+ </Box>
43
+ <Box marginLeft={4}>
44
+ <Text dimColor>{plugin.description}</Text>
45
+ </Box>
46
+ </Box>
47
+ );
48
+ })}
49
+ </Box>
50
+ );
51
+ };
@@ -0,0 +1,189 @@
1
+ import React from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { ViewType } from "./PluginManagerTypes.js";
4
+ import { usePluginManager } from "../hooks/usePluginManager.js";
5
+ import { DiscoverView } from "./DiscoverView.js";
6
+ import { InstalledView } from "./InstalledView.js";
7
+ import { MarketplaceView } from "./MarketplaceView.js";
8
+ import { MarketplaceDetail } from "./MarketplaceDetail.js";
9
+ import { PluginDetail } from "./PluginDetail.js";
10
+ import { MarketplaceAddForm } from "./MarketplaceAddForm.js";
11
+ import { PluginManagerContext } from "../contexts/PluginManagerContext.js";
12
+
13
+ export const PluginManagerShell: React.FC<{ children?: React.ReactNode }> = ({
14
+ children,
15
+ }) => {
16
+ const pluginManager = usePluginManager();
17
+ const { state, actions, discoverablePlugins } = pluginManager;
18
+
19
+ const setView = (view: ViewType) => {
20
+ if (
21
+ view !== "PLUGIN_DETAIL" &&
22
+ view !== "MARKETPLACE_DETAIL" &&
23
+ view !== "ADD_MARKETPLACE"
24
+ ) {
25
+ actions.setSelectedId(null);
26
+ }
27
+ actions.setView(view);
28
+ };
29
+
30
+ useInput((input, key) => {
31
+ if (key.tab) {
32
+ const views: ViewType[] = ["DISCOVER", "INSTALLED", "MARKETPLACES"];
33
+ const currentIndex = views.indexOf(
34
+ state.currentView === "PLUGIN_DETAIL"
35
+ ? discoverablePlugins.find(
36
+ (p) => `${p.name}@${p.marketplace}` === state.selectedId,
37
+ )
38
+ ? "DISCOVER"
39
+ : "INSTALLED"
40
+ : state.currentView === "MARKETPLACE_DETAIL" ||
41
+ state.currentView === "ADD_MARKETPLACE"
42
+ ? "MARKETPLACES"
43
+ : state.currentView,
44
+ );
45
+
46
+ let nextIndex;
47
+ if (key.shift) {
48
+ nextIndex = (currentIndex - 1 + views.length) % views.length;
49
+ } else {
50
+ nextIndex = (currentIndex + 1) % views.length;
51
+ }
52
+ setView(views[nextIndex]);
53
+ }
54
+ if (key.escape) {
55
+ if (state.currentView === "PLUGIN_DETAIL") {
56
+ const isFromDiscover = discoverablePlugins.find(
57
+ (p) => `${p.name}@${p.marketplace}` === state.selectedId,
58
+ );
59
+ setView(isFromDiscover ? "DISCOVER" : "INSTALLED");
60
+ } else if (
61
+ state.currentView === "MARKETPLACE_DETAIL" ||
62
+ state.currentView === "ADD_MARKETPLACE"
63
+ ) {
64
+ setView("MARKETPLACES");
65
+ }
66
+ }
67
+ });
68
+
69
+ const renderView = () => {
70
+ if (state.isLoading && !state.selectedId) {
71
+ return (
72
+ <Box padding={1}>
73
+ <Text color="yellow">Loading...</Text>
74
+ </Box>
75
+ );
76
+ }
77
+
78
+ switch (state.currentView) {
79
+ case "DISCOVER":
80
+ return <DiscoverView />;
81
+ case "INSTALLED":
82
+ return <InstalledView />;
83
+ case "MARKETPLACES":
84
+ return <MarketplaceView />;
85
+ case "MARKETPLACE_DETAIL":
86
+ return <MarketplaceDetail />;
87
+ case "PLUGIN_DETAIL":
88
+ return <PluginDetail />;
89
+ case "ADD_MARKETPLACE":
90
+ return <MarketplaceAddForm />;
91
+ default:
92
+ return <DiscoverView />;
93
+ }
94
+ };
95
+
96
+ return (
97
+ <PluginManagerContext.Provider value={pluginManager}>
98
+ <Box
99
+ flexDirection="column"
100
+ width="100%"
101
+ height="100%"
102
+ borderStyle="round"
103
+ borderColor="cyan"
104
+ >
105
+ <Box
106
+ paddingX={1}
107
+ borderStyle="single"
108
+ borderBottom={true}
109
+ borderTop={false}
110
+ borderLeft={false}
111
+ borderRight={false}
112
+ >
113
+ <Box marginRight={2}>
114
+ <Text bold color="cyan">
115
+ Plugin Manager
116
+ </Text>
117
+ </Box>
118
+ <Box>
119
+ <Text
120
+ color={
121
+ state.currentView === "DISCOVER" ||
122
+ (state.currentView === "PLUGIN_DETAIL" &&
123
+ !!discoverablePlugins.find(
124
+ (p) => `${p.name}@${p.marketplace}` === state.selectedId,
125
+ ))
126
+ ? "yellow"
127
+ : undefined
128
+ }
129
+ >
130
+ {" "}
131
+ Discover{" "}
132
+ </Text>
133
+ <Text
134
+ color={
135
+ state.currentView === "INSTALLED" ||
136
+ (state.currentView === "PLUGIN_DETAIL" &&
137
+ !discoverablePlugins.find(
138
+ (p) => `${p.name}@${p.marketplace}` === state.selectedId,
139
+ ))
140
+ ? "yellow"
141
+ : undefined
142
+ }
143
+ >
144
+ {" "}
145
+ Installed{" "}
146
+ </Text>
147
+ <Text
148
+ color={
149
+ state.currentView === "MARKETPLACES" ||
150
+ state.currentView === "MARKETPLACE_DETAIL" ||
151
+ state.currentView === "ADD_MARKETPLACE"
152
+ ? "yellow"
153
+ : undefined
154
+ }
155
+ >
156
+ {" "}
157
+ Marketplaces{" "}
158
+ </Text>
159
+ </Box>
160
+ </Box>
161
+
162
+ <Box flexGrow={1} flexDirection="column" padding={1}>
163
+ {renderView()}
164
+ {children}
165
+ </Box>
166
+
167
+ <Box
168
+ paddingX={1}
169
+ borderStyle="single"
170
+ borderTop={true}
171
+ borderBottom={false}
172
+ borderLeft={false}
173
+ borderRight={false}
174
+ >
175
+ <Text dimColor>
176
+ {state.isLoading
177
+ ? "Loading..."
178
+ : "Use Tab to switch views, arrows to navigate, Enter to select, Esc to go back"}
179
+ </Text>
180
+ {state.error && (
181
+ <Box marginLeft={2}>
182
+ <Text color="red">Error: {state.error}</Text>
183
+ </Box>
184
+ )}
185
+ </Box>
186
+ </Box>
187
+ </PluginManagerContext.Provider>
188
+ );
189
+ };
@@ -0,0 +1,47 @@
1
+ import {
2
+ KnownMarketplace,
3
+ InstalledPlugin,
4
+ MarketplacePluginEntry,
5
+ } from "wave-agent-sdk";
6
+
7
+ export type ViewType =
8
+ | "DISCOVER"
9
+ | "INSTALLED"
10
+ | "MARKETPLACES"
11
+ | "PLUGIN_DETAIL"
12
+ | "MARKETPLACE_DETAIL"
13
+ | "ADD_MARKETPLACE";
14
+
15
+ export interface PluginManagerState {
16
+ currentView: ViewType;
17
+ selectedId: string | null; // Plugin name or Marketplace name
18
+ isLoading: boolean;
19
+ error: string | null;
20
+ searchQuery: string;
21
+ }
22
+
23
+ export interface PluginManagerContextType {
24
+ state: PluginManagerState;
25
+ marketplaces: KnownMarketplace[];
26
+ installedPlugins: (InstalledPlugin & { enabled: boolean })[];
27
+ discoverablePlugins: (MarketplacePluginEntry & {
28
+ marketplace: string;
29
+ installed: boolean;
30
+ version?: string;
31
+ })[];
32
+ actions: {
33
+ setView: (view: ViewType) => void;
34
+ setSelectedId: (id: string | null) => void;
35
+ addMarketplace: (source: string) => Promise<void>;
36
+ removeMarketplace: (name: string) => Promise<void>;
37
+ updateMarketplace: (name: string) => Promise<void>;
38
+ installPlugin: (
39
+ name: string,
40
+ marketplace: string,
41
+ scope?: "user" | "project" | "local",
42
+ ) => Promise<void>;
43
+ uninstallPlugin: (name: string, marketplace: string) => Promise<void>;
44
+ updatePlugin: (name: string, marketplace: string) => Promise<void>;
45
+ refresh: () => Promise<void>;
46
+ };
47
+ }
@@ -0,0 +1,114 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import type { Message, TextBlock } from "wave-agent-sdk";
4
+
5
+ export interface RewindCommandProps {
6
+ messages: Message[];
7
+ onSelect: (index: number) => void;
8
+ onCancel: () => void;
9
+ }
10
+
11
+ export const RewindCommand: React.FC<RewindCommandProps> = ({
12
+ messages,
13
+ onSelect,
14
+ onCancel,
15
+ }) => {
16
+ // Filter user messages as checkpoints
17
+ const checkpoints = messages
18
+ .map((msg, index) => ({ msg, index }))
19
+ .filter(({ msg }) => msg.role === "user");
20
+
21
+ const [selectedIndex, setSelectedIndex] = useState(checkpoints.length - 1);
22
+
23
+ useInput((input, key) => {
24
+ if (key.return) {
25
+ if (checkpoints.length > 0 && selectedIndex >= 0) {
26
+ onSelect(checkpoints[selectedIndex].index);
27
+ }
28
+ return;
29
+ }
30
+
31
+ if (key.escape) {
32
+ onCancel();
33
+ return;
34
+ }
35
+
36
+ if (key.upArrow) {
37
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
38
+ return;
39
+ }
40
+
41
+ if (key.downArrow) {
42
+ setSelectedIndex(Math.min(checkpoints.length - 1, selectedIndex + 1));
43
+ return;
44
+ }
45
+ });
46
+
47
+ if (checkpoints.length === 0) {
48
+ return (
49
+ <Box
50
+ flexDirection="column"
51
+ paddingX={1}
52
+ borderStyle="single"
53
+ borderColor="yellow"
54
+ borderLeft={false}
55
+ borderRight={false}
56
+ >
57
+ <Text color="yellow">No user messages found to rewind to.</Text>
58
+ <Text dimColor>Press Escape to cancel</Text>
59
+ </Box>
60
+ );
61
+ }
62
+
63
+ return (
64
+ <Box
65
+ flexDirection="column"
66
+ paddingX={1}
67
+ gap={1}
68
+ borderStyle="single"
69
+ borderColor="cyan"
70
+ borderLeft={false}
71
+ borderRight={false}
72
+ >
73
+ <Box>
74
+ <Text color="cyan" bold>
75
+ Rewind: Select a message to revert to
76
+ </Text>
77
+ </Box>
78
+
79
+ <Box flexDirection="column">
80
+ {checkpoints.map((checkpoint, index) => {
81
+ const isSelected = index === selectedIndex;
82
+ const content = checkpoint.msg.blocks
83
+ .filter((b): b is TextBlock => b.type === "text")
84
+ .map((b) => b.content)
85
+ .join(" ")
86
+ .substring(0, 60);
87
+
88
+ return (
89
+ <Box key={checkpoint.index}>
90
+ <Text
91
+ color={isSelected ? "black" : "white"}
92
+ backgroundColor={isSelected ? "cyan" : undefined}
93
+ >
94
+ {isSelected ? "▶ " : " "}[{checkpoint.index}]{" "}
95
+ {content || "(No text content)"}
96
+ {index === checkpoints.length - 1 ? " (Latest)" : ""}
97
+ </Text>
98
+ </Box>
99
+ );
100
+ })}
101
+ </Box>
102
+
103
+ <Box>
104
+ <Text dimColor>↑↓ navigate • Enter to rewind • Esc to cancel</Text>
105
+ </Box>
106
+ <Box>
107
+ <Text color="red" dimColor>
108
+ ⚠️ Warning: This will delete all subsequent messages and revert file
109
+ changes.
110
+ </Text>
111
+ </Box>
112
+ </Box>
113
+ );
114
+ };