wave-code 0.18.2 → 0.18.4
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/CommandSelector.d.ts.map +1 -1
- package/dist/components/CommandSelector.js +9 -13
- package/dist/components/ConfirmationSelector.d.ts.map +1 -1
- package/dist/components/ConfirmationSelector.js +0 -1
- package/dist/components/DiscoverView.d.ts.map +1 -1
- package/dist/components/DiscoverView.js +10 -6
- package/dist/components/FileSelector.js +8 -8
- package/dist/components/HistorySearch.d.ts.map +1 -1
- package/dist/components/HistorySearch.js +6 -13
- package/dist/components/InstalledView.d.ts.map +1 -1
- package/dist/components/InstalledView.js +10 -6
- package/dist/components/LoginCommand.d.ts.map +1 -1
- package/dist/components/LoginCommand.js +14 -11
- package/dist/components/MarketplaceDetail.d.ts.map +1 -1
- package/dist/components/MarketplaceDetail.js +11 -7
- package/dist/components/MarketplaceView.d.ts.map +1 -1
- package/dist/components/MarketplaceView.js +10 -6
- package/dist/components/ModelSelector.d.ts.map +1 -1
- package/dist/components/ModelSelector.js +9 -15
- package/dist/components/SessionSelector.d.ts +5 -3
- package/dist/components/SessionSelector.d.ts.map +1 -1
- package/dist/components/SessionSelector.js +11 -7
- package/dist/components/WorktreeExitPrompt.d.ts.map +1 -1
- package/dist/components/WorktreeExitPrompt.js +3 -3
- package/dist/reducers/questionReducer.d.ts +0 -7
- package/dist/reducers/questionReducer.d.ts.map +1 -1
- package/dist/reducers/questionReducer.js +2 -1
- package/dist/reducers/selectorReducer.d.ts +7 -5
- package/dist/reducers/selectorReducer.d.ts.map +1 -1
- package/dist/reducers/selectorReducer.js +4 -2
- package/package.json +2 -2
- package/src/components/CommandSelector.tsx +11 -15
- package/src/components/ConfirmationSelector.tsx +0 -1
- package/src/components/DiscoverView.tsx +22 -7
- package/src/components/FileSelector.tsx +9 -9
- package/src/components/HistorySearch.tsx +7 -14
- package/src/components/InstalledView.tsx +23 -9
- package/src/components/LoginCommand.tsx +33 -36
- package/src/components/MarketplaceDetail.tsx +25 -10
- package/src/components/MarketplaceView.tsx +16 -7
- package/src/components/ModelSelector.tsx +13 -16
- package/src/components/SessionSelector.tsx +19 -9
- package/src/components/WorktreeExitPrompt.tsx +7 -4
- package/src/reducers/questionReducer.ts +2 -6
- package/src/reducers/selectorReducer.ts +20 -10
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { Key } from "ink";
|
|
2
|
-
export interface SelectorState {
|
|
2
|
+
export interface SelectorState<T = unknown> {
|
|
3
3
|
selectedIndex: number;
|
|
4
4
|
pendingDecision: "select" | "insert" | "cancel" | null;
|
|
5
|
+
items: T[];
|
|
5
6
|
}
|
|
6
|
-
export type SelectorAction = {
|
|
7
|
+
export type SelectorAction<T = unknown> = {
|
|
7
8
|
type: "MOVE_UP";
|
|
8
9
|
} | {
|
|
9
10
|
type: "MOVE_DOWN";
|
|
10
|
-
maxIndex: number;
|
|
11
11
|
} | {
|
|
12
12
|
type: "RESET_INDEX";
|
|
13
|
+
} | {
|
|
14
|
+
type: "SET_ITEMS";
|
|
15
|
+
items: T[];
|
|
13
16
|
} | {
|
|
14
17
|
type: "HANDLE_KEY";
|
|
15
18
|
key: Key;
|
|
16
|
-
maxIndex: number;
|
|
17
19
|
hasInsert: boolean;
|
|
18
20
|
} | {
|
|
19
21
|
type: "CLEAR_DECISION";
|
|
20
22
|
};
|
|
21
|
-
export declare function selectorReducer(state: SelectorState
|
|
23
|
+
export declare function selectorReducer<T = unknown>(state: SelectorState<T>, action: SelectorAction<T>): SelectorState<T>;
|
|
22
24
|
//# sourceMappingURL=selectorReducer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selectorReducer.d.ts","sourceRoot":"","sources":["../../src/reducers/selectorReducer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,WAAW,aAAa;
|
|
1
|
+
{"version":3,"file":"selectorReducer.d.ts","sourceRoot":"","sources":["../../src/reducers/selectorReducer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;IACvD,KAAK,EAAE,CAAC,EAAE,CAAC;CACZ;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAClC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,CAAC,EAAE,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE/B,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EACzC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,GACxB,aAAa,CAAC,CAAC,CAAC,CA+ClB"}
|
|
@@ -5,10 +5,12 @@ export function selectorReducer(state, action) {
|
|
|
5
5
|
case "MOVE_DOWN":
|
|
6
6
|
return {
|
|
7
7
|
...state,
|
|
8
|
-
selectedIndex: Math.min(
|
|
8
|
+
selectedIndex: Math.min(state.items.length - 1, state.selectedIndex + 1),
|
|
9
9
|
};
|
|
10
10
|
case "RESET_INDEX":
|
|
11
11
|
return { ...state, selectedIndex: 0 };
|
|
12
|
+
case "SET_ITEMS":
|
|
13
|
+
return { ...state, items: action.items, selectedIndex: 0 };
|
|
12
14
|
case "HANDLE_KEY":
|
|
13
15
|
if (action.key.upArrow) {
|
|
14
16
|
return {
|
|
@@ -19,7 +21,7 @@ export function selectorReducer(state, action) {
|
|
|
19
21
|
if (action.key.downArrow) {
|
|
20
22
|
return {
|
|
21
23
|
...state,
|
|
22
|
-
selectedIndex: Math.min(
|
|
24
|
+
selectedIndex: Math.min(state.items.length - 1, state.selectedIndex + 1),
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
27
|
if (action.key.return) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.4",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"semver": "^7.7.4",
|
|
43
43
|
"yargs": "^17.7.2",
|
|
44
44
|
"zod": "^3.23.8",
|
|
45
|
-
"wave-agent-sdk": "0.18.
|
|
45
|
+
"wave-agent-sdk": "0.18.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/react": "^19.1.8",
|
|
@@ -23,20 +23,16 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
23
23
|
commands = [],
|
|
24
24
|
}) => {
|
|
25
25
|
const MAX_VISIBLE_ITEMS = 3;
|
|
26
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
26
|
+
const [state, dispatch] = useReducer(selectorReducer<SlashCommand>, {
|
|
27
27
|
selectedIndex: 0,
|
|
28
28
|
pendingDecision: null,
|
|
29
|
-
|
|
29
|
+
items: [],
|
|
30
|
+
} as SelectorState<SlashCommand>);
|
|
30
31
|
|
|
31
|
-
const { selectedIndex, pendingDecision } = state;
|
|
32
|
-
|
|
33
|
-
// Reset selected index when search query changes
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
dispatch({ type: "RESET_INDEX" });
|
|
36
|
-
}, [searchQuery]);
|
|
32
|
+
const { selectedIndex, pendingDecision, items: filteredCommands } = state;
|
|
37
33
|
|
|
38
34
|
// Merge agent commands and local commands, memoized to stabilize useEffect dependencies
|
|
39
|
-
const
|
|
35
|
+
const computedCommands = useMemo(() => {
|
|
40
36
|
const allCommands = [...AVAILABLE_COMMANDS, ...commands];
|
|
41
37
|
return allCommands.filter(
|
|
42
38
|
(command) =>
|
|
@@ -45,6 +41,11 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
45
41
|
);
|
|
46
42
|
}, [searchQuery, commands]);
|
|
47
43
|
|
|
44
|
+
// Sync computed commands into reducer state (SET_ITEMS also resets index)
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
dispatch({ type: "SET_ITEMS", items: computedCommands });
|
|
47
|
+
}, [computedCommands]);
|
|
48
|
+
|
|
48
49
|
// Handle decisions from reducer
|
|
49
50
|
useEffect(() => {
|
|
50
51
|
if (!pendingDecision) return;
|
|
@@ -91,12 +92,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
91
92
|
);
|
|
92
93
|
|
|
93
94
|
useInput((input, key) => {
|
|
94
|
-
dispatch({
|
|
95
|
-
type: "HANDLE_KEY",
|
|
96
|
-
key,
|
|
97
|
-
maxIndex: filteredCommands.length - 1,
|
|
98
|
-
hasInsert: !!onInsert,
|
|
99
|
-
});
|
|
95
|
+
dispatch({ type: "HANDLE_KEY", key, hasInsert: !!onInsert });
|
|
100
96
|
});
|
|
101
97
|
|
|
102
98
|
if (filteredCommands.length === 0) {
|
|
@@ -1,37 +1,52 @@
|
|
|
1
1
|
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, useInput } from "ink";
|
|
3
|
+
import type { MarketplacePluginEntry } from "wave-agent-sdk";
|
|
3
4
|
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
5
|
import { PluginList } from "./PluginList.js";
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
selectorReducer,
|
|
8
|
+
type SelectorState,
|
|
9
|
+
} from "../reducers/selectorReducer.js";
|
|
10
|
+
|
|
11
|
+
type DiscoverablePlugin = MarketplacePluginEntry & {
|
|
12
|
+
marketplace: string;
|
|
13
|
+
installed: boolean;
|
|
14
|
+
version?: string;
|
|
15
|
+
};
|
|
6
16
|
|
|
7
17
|
export const DiscoverView: React.FC = () => {
|
|
8
18
|
const { discoverablePlugins, actions } = usePluginManagerContext();
|
|
9
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
19
|
+
const [state, dispatch] = useReducer(selectorReducer<DiscoverablePlugin>, {
|
|
10
20
|
selectedIndex: 0,
|
|
11
21
|
pendingDecision: null,
|
|
12
|
-
|
|
22
|
+
items: [],
|
|
23
|
+
} as SelectorState<DiscoverablePlugin>);
|
|
13
24
|
|
|
14
|
-
const { selectedIndex, pendingDecision } = state;
|
|
25
|
+
const { selectedIndex, pendingDecision, items } = state;
|
|
26
|
+
|
|
27
|
+
// Sync plugins into reducer state
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
dispatch({ type: "SET_ITEMS", items: discoverablePlugins });
|
|
30
|
+
}, [discoverablePlugins]);
|
|
15
31
|
|
|
16
32
|
useInput((_input, key) => {
|
|
17
33
|
dispatch({
|
|
18
34
|
type: "HANDLE_KEY",
|
|
19
35
|
key,
|
|
20
|
-
maxIndex: discoverablePlugins.length - 1,
|
|
21
36
|
hasInsert: false,
|
|
22
37
|
});
|
|
23
38
|
});
|
|
24
39
|
|
|
25
40
|
useEffect(() => {
|
|
26
41
|
if (pendingDecision === "select") {
|
|
27
|
-
const plugin =
|
|
42
|
+
const plugin = items[selectedIndex];
|
|
28
43
|
if (plugin) {
|
|
29
44
|
actions.setSelectedId(`${plugin.name}@${plugin.marketplace}`);
|
|
30
45
|
actions.setView("PLUGIN_DETAIL");
|
|
31
46
|
}
|
|
32
47
|
dispatch({ type: "CLEAR_DECISION" });
|
|
33
48
|
}
|
|
34
|
-
}, [pendingDecision, selectedIndex,
|
|
49
|
+
}, [pendingDecision, selectedIndex, items, actions]);
|
|
35
50
|
|
|
36
51
|
return (
|
|
37
52
|
<Box flexDirection="column">
|
|
@@ -23,16 +23,17 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
|
|
|
23
23
|
onSelect,
|
|
24
24
|
onCancel,
|
|
25
25
|
}) => {
|
|
26
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
26
|
+
const [state, dispatch] = useReducer(selectorReducer<FileItem>, {
|
|
27
27
|
selectedIndex: 0,
|
|
28
28
|
pendingDecision: null,
|
|
29
|
-
|
|
29
|
+
items: [],
|
|
30
|
+
} as SelectorState<FileItem>);
|
|
30
31
|
|
|
31
|
-
const { selectedIndex, pendingDecision } = state;
|
|
32
|
+
const { selectedIndex, pendingDecision, items: filesInState } = state;
|
|
32
33
|
|
|
33
|
-
//
|
|
34
|
+
// Sync files from props into reducer state (SET_ITEMS also resets index)
|
|
34
35
|
useEffect(() => {
|
|
35
|
-
dispatch({ type: "
|
|
36
|
+
dispatch({ type: "SET_ITEMS", items: files });
|
|
36
37
|
}, [files]);
|
|
37
38
|
|
|
38
39
|
// Handle decisions from reducer
|
|
@@ -40,21 +41,20 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
|
|
|
40
41
|
if (!pendingDecision) return;
|
|
41
42
|
|
|
42
43
|
if (pendingDecision === "select" || pendingDecision === "insert") {
|
|
43
|
-
if (
|
|
44
|
-
onSelect(
|
|
44
|
+
if (filesInState.length > 0 && selectedIndex < filesInState.length) {
|
|
45
|
+
onSelect(filesInState[selectedIndex].path);
|
|
45
46
|
}
|
|
46
47
|
} else if (pendingDecision === "cancel") {
|
|
47
48
|
onCancel();
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
dispatch({ type: "CLEAR_DECISION" });
|
|
51
|
-
}, [pendingDecision, selectedIndex,
|
|
52
|
+
}, [pendingDecision, selectedIndex, filesInState, onSelect, onCancel]);
|
|
52
53
|
|
|
53
54
|
useInput((input, key) => {
|
|
54
55
|
dispatch({
|
|
55
56
|
type: "HANDLE_KEY",
|
|
56
57
|
key,
|
|
57
|
-
maxIndex: files.length - 1,
|
|
58
58
|
hasInsert: true, // For FileSelector, Tab is also select
|
|
59
59
|
});
|
|
60
60
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useReducer, useEffect
|
|
1
|
+
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { PromptHistoryManager, type PromptEntry } from "wave-agent-sdk";
|
|
4
4
|
import {
|
|
@@ -18,20 +18,18 @@ export const HistorySearch: React.FC<HistorySearchProps> = ({
|
|
|
18
18
|
onCancel,
|
|
19
19
|
}) => {
|
|
20
20
|
const MAX_VISIBLE_ITEMS = 5;
|
|
21
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
21
|
+
const [state, dispatch] = useReducer(selectorReducer<PromptEntry>, {
|
|
22
22
|
selectedIndex: 0,
|
|
23
23
|
pendingDecision: null,
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
items: [],
|
|
25
|
+
} as SelectorState<PromptEntry>);
|
|
26
26
|
|
|
27
|
-
const { selectedIndex, pendingDecision } = state;
|
|
27
|
+
const { selectedIndex, pendingDecision, items: entries } = state;
|
|
28
28
|
|
|
29
29
|
useEffect(() => {
|
|
30
30
|
const fetchHistory = async () => {
|
|
31
31
|
const results = await PromptHistoryManager.searchHistory(searchQuery);
|
|
32
|
-
|
|
33
|
-
setEntries(limitedResults);
|
|
34
|
-
dispatch({ type: "RESET_INDEX" });
|
|
32
|
+
dispatch({ type: "SET_ITEMS", items: results.slice(0, 20) });
|
|
35
33
|
};
|
|
36
34
|
fetchHistory();
|
|
37
35
|
}, [searchQuery]);
|
|
@@ -52,12 +50,7 @@ export const HistorySearch: React.FC<HistorySearchProps> = ({
|
|
|
52
50
|
}, [pendingDecision, selectedIndex, entries, onSelect, onCancel]);
|
|
53
51
|
|
|
54
52
|
useInput((input, key) => {
|
|
55
|
-
dispatch({
|
|
56
|
-
type: "HANDLE_KEY",
|
|
57
|
-
key,
|
|
58
|
-
maxIndex: entries.length - 1,
|
|
59
|
-
hasInsert: false,
|
|
60
|
-
});
|
|
53
|
+
dispatch({ type: "HANDLE_KEY", key, hasInsert: false });
|
|
61
54
|
});
|
|
62
55
|
|
|
63
56
|
if (entries.length === 0) {
|
|
@@ -1,36 +1,50 @@
|
|
|
1
1
|
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import type { InstalledPlugin } from "wave-agent-sdk";
|
|
3
4
|
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
selectorReducer,
|
|
7
|
+
type SelectorState,
|
|
8
|
+
} from "../reducers/selectorReducer.js";
|
|
9
|
+
|
|
10
|
+
type InstalledPluginWithEnabled = InstalledPlugin & { enabled: boolean };
|
|
5
11
|
|
|
6
12
|
export const InstalledView: React.FC = () => {
|
|
7
13
|
const { installedPlugins, actions } = usePluginManagerContext();
|
|
8
|
-
const [state, dispatch] = useReducer(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
const [state, dispatch] = useReducer(
|
|
15
|
+
selectorReducer<InstalledPluginWithEnabled>,
|
|
16
|
+
{
|
|
17
|
+
selectedIndex: 0,
|
|
18
|
+
pendingDecision: null,
|
|
19
|
+
items: [],
|
|
20
|
+
} as SelectorState<InstalledPluginWithEnabled>,
|
|
21
|
+
);
|
|
12
22
|
|
|
13
|
-
const { selectedIndex, pendingDecision } = state;
|
|
23
|
+
const { selectedIndex, pendingDecision, items } = state;
|
|
24
|
+
|
|
25
|
+
// Sync plugins into reducer state
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
dispatch({ type: "SET_ITEMS", items: installedPlugins });
|
|
28
|
+
}, [installedPlugins]);
|
|
14
29
|
|
|
15
30
|
useInput((_input, key) => {
|
|
16
31
|
dispatch({
|
|
17
32
|
type: "HANDLE_KEY",
|
|
18
33
|
key,
|
|
19
|
-
maxIndex: installedPlugins.length - 1,
|
|
20
34
|
hasInsert: false,
|
|
21
35
|
});
|
|
22
36
|
});
|
|
23
37
|
|
|
24
38
|
useEffect(() => {
|
|
25
39
|
if (pendingDecision === "select") {
|
|
26
|
-
const plugin =
|
|
40
|
+
const plugin = items[selectedIndex];
|
|
27
41
|
if (plugin) {
|
|
28
42
|
actions.setSelectedId(`${plugin.name}@${plugin.marketplace}`);
|
|
29
43
|
actions.setView("PLUGIN_DETAIL");
|
|
30
44
|
}
|
|
31
45
|
dispatch({ type: "CLEAR_DECISION" });
|
|
32
46
|
}
|
|
33
|
-
}, [pendingDecision, selectedIndex,
|
|
47
|
+
}, [pendingDecision, selectedIndex, items, actions]);
|
|
34
48
|
|
|
35
49
|
if (installedPlugins.length === 0) {
|
|
36
50
|
return (
|
|
@@ -36,49 +36,46 @@ export const LoginCommand: React.FC<LoginCommandProps> = ({ onCancel }) => {
|
|
|
36
36
|
const tokenResolveRef = useRef<((token: string) => void) | null>(null);
|
|
37
37
|
const tokenRejectRef = useRef<((err: Error) => void) | null>(null);
|
|
38
38
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// Single useInput handler — branches on isLoading to avoid the
|
|
40
|
+
// isActive transition race that drops stdin between hook swaps.
|
|
41
|
+
useInput((input, key) => {
|
|
42
|
+
if (!isLoadingRef.current) {
|
|
43
|
+
// Pre-loading mode: Enter starts login, Esc cancels
|
|
42
44
|
if (key.escape) {
|
|
43
45
|
onCancel();
|
|
44
46
|
}
|
|
45
|
-
if (key.return
|
|
47
|
+
if (key.return) {
|
|
46
48
|
handleEnter();
|
|
47
49
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Empty: just clear, keep waiting
|
|
66
|
-
setTokenInput("");
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
// Backspace
|
|
71
|
-
if (key.backspace && tokenInput.length > 0) {
|
|
72
|
-
setTokenInput((prev) => prev.slice(0, -1));
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
// Regular character input (single or pasted multi-char)
|
|
76
|
-
if (input && !key.ctrl && !key.meta && !key.return && input.length > 0) {
|
|
77
|
-
setTokenInput((prev) => prev + input);
|
|
53
|
+
// Token input mode: capture keystrokes while loading
|
|
54
|
+
if (key.escape) {
|
|
55
|
+
tokenRejectRef.current?.(new Error("cancelled"));
|
|
56
|
+
onCancel();
|
|
57
|
+
}
|
|
58
|
+
if (key.return) {
|
|
59
|
+
// Submit token
|
|
60
|
+
const trimmed = tokenInput.trim();
|
|
61
|
+
if (trimmed) {
|
|
62
|
+
tokenResolveRef.current?.(trimmed);
|
|
63
|
+
} else {
|
|
64
|
+
// Empty: just clear, keep waiting
|
|
65
|
+
setTokenInput("");
|
|
78
66
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Backspace
|
|
70
|
+
if (key.backspace && tokenInput.length > 0) {
|
|
71
|
+
setTokenInput((prev) => prev.slice(0, -1));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Regular character input (single or pasted multi-char)
|
|
75
|
+
if (input && !key.ctrl && !key.meta && !key.return && input.length > 0) {
|
|
76
|
+
setTokenInput((prev) => prev + input);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
82
79
|
|
|
83
80
|
const handleEnter = async () => {
|
|
84
81
|
if (isLoadingRef.current) return;
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import React, { useReducer, useEffect, useMemo } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
selectorReducer,
|
|
6
|
+
type SelectorState,
|
|
7
|
+
} from "../reducers/selectorReducer.js";
|
|
5
8
|
|
|
6
9
|
export const MarketplaceDetail: React.FC = () => {
|
|
7
10
|
const { state, marketplaces, actions } = usePluginManagerContext();
|
|
8
|
-
const [selectorState, dispatch] = useReducer(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
const [selectorState, dispatch] = useReducer(
|
|
12
|
+
selectorReducer<{ id: string; label: string }>,
|
|
13
|
+
{
|
|
14
|
+
selectedIndex: 0,
|
|
15
|
+
pendingDecision: null,
|
|
16
|
+
items: [],
|
|
17
|
+
} as SelectorState<{ id: string; label: string }>,
|
|
18
|
+
);
|
|
12
19
|
|
|
13
|
-
const {
|
|
20
|
+
const {
|
|
21
|
+
selectedIndex: selectedActionIndex,
|
|
22
|
+
pendingDecision,
|
|
23
|
+
items: actionsList,
|
|
24
|
+
} = selectorState;
|
|
14
25
|
|
|
15
26
|
const marketplace = marketplaces.find((m) => m.name === state.selectedId);
|
|
16
27
|
|
|
@@ -27,13 +38,17 @@ export const MarketplaceDetail: React.FC = () => {
|
|
|
27
38
|
[marketplace?.autoUpdate],
|
|
28
39
|
);
|
|
29
40
|
|
|
41
|
+
// Sync ACTIONS into reducer state
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
dispatch({ type: "SET_ITEMS", items: [...ACTIONS] });
|
|
44
|
+
}, [ACTIONS]);
|
|
45
|
+
|
|
30
46
|
useInput((_input, key) => {
|
|
31
47
|
if (state.isLoading && !key.escape) return;
|
|
32
48
|
|
|
33
49
|
dispatch({
|
|
34
50
|
type: "HANDLE_KEY",
|
|
35
51
|
key,
|
|
36
|
-
maxIndex: ACTIONS.length - 1,
|
|
37
52
|
hasInsert: false,
|
|
38
53
|
});
|
|
39
54
|
});
|
|
@@ -42,12 +57,12 @@ export const MarketplaceDetail: React.FC = () => {
|
|
|
42
57
|
if (!pendingDecision) return;
|
|
43
58
|
|
|
44
59
|
if (pendingDecision === "select" && marketplace && !state.isLoading) {
|
|
45
|
-
const action =
|
|
60
|
+
const action = actionsList[selectedActionIndex]?.id;
|
|
46
61
|
if (action === "toggle-auto-update") {
|
|
47
62
|
actions.toggleAutoUpdate(marketplace.name, !marketplace.autoUpdate);
|
|
48
63
|
} else if (action === "update") {
|
|
49
64
|
actions.updateMarketplace(marketplace.name);
|
|
50
|
-
} else {
|
|
65
|
+
} else if (action === "remove") {
|
|
51
66
|
actions.removeMarketplace(marketplace.name);
|
|
52
67
|
}
|
|
53
68
|
} else if (pendingDecision === "cancel") {
|
|
@@ -58,10 +73,10 @@ export const MarketplaceDetail: React.FC = () => {
|
|
|
58
73
|
}, [
|
|
59
74
|
pendingDecision,
|
|
60
75
|
selectedActionIndex,
|
|
76
|
+
actionsList,
|
|
61
77
|
marketplace,
|
|
62
78
|
state.isLoading,
|
|
63
79
|
actions,
|
|
64
|
-
ACTIONS,
|
|
65
80
|
]);
|
|
66
81
|
|
|
67
82
|
if (!marketplace) {
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import type { KnownMarketplace } from "wave-agent-sdk";
|
|
3
4
|
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
selectorReducer,
|
|
7
|
+
type SelectorState,
|
|
8
|
+
} from "../reducers/selectorReducer.js";
|
|
5
9
|
|
|
6
10
|
import { MarketplaceList } from "./MarketplaceList.js";
|
|
7
11
|
|
|
8
12
|
export const MarketplaceView: React.FC = () => {
|
|
9
13
|
const { marketplaces, actions } = usePluginManagerContext();
|
|
10
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
14
|
+
const [state, dispatch] = useReducer(selectorReducer<KnownMarketplace>, {
|
|
11
15
|
selectedIndex: 0,
|
|
12
16
|
pendingDecision: null,
|
|
13
|
-
|
|
17
|
+
items: [],
|
|
18
|
+
} as SelectorState<KnownMarketplace>);
|
|
19
|
+
|
|
20
|
+
const { selectedIndex, pendingDecision, items } = state;
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
// Sync marketplaces into reducer state
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
dispatch({ type: "SET_ITEMS", items: marketplaces });
|
|
25
|
+
}, [marketplaces]);
|
|
16
26
|
|
|
17
27
|
useInput((input, key) => {
|
|
18
28
|
if (input === "a") {
|
|
@@ -23,21 +33,20 @@ export const MarketplaceView: React.FC = () => {
|
|
|
23
33
|
dispatch({
|
|
24
34
|
type: "HANDLE_KEY",
|
|
25
35
|
key,
|
|
26
|
-
maxIndex: marketplaces.length - 1,
|
|
27
36
|
hasInsert: false,
|
|
28
37
|
});
|
|
29
38
|
});
|
|
30
39
|
|
|
31
40
|
useEffect(() => {
|
|
32
41
|
if (pendingDecision === "select") {
|
|
33
|
-
const mk =
|
|
42
|
+
const mk = items[selectedIndex];
|
|
34
43
|
if (mk) {
|
|
35
44
|
actions.setSelectedId(mk.name);
|
|
36
45
|
actions.setView("MARKETPLACE_DETAIL");
|
|
37
46
|
}
|
|
38
47
|
dispatch({ type: "CLEAR_DECISION" });
|
|
39
48
|
}
|
|
40
|
-
}, [pendingDecision, selectedIndex,
|
|
49
|
+
}, [pendingDecision, selectedIndex, items, actions]);
|
|
41
50
|
|
|
42
51
|
return (
|
|
43
52
|
<Box flexDirection="column">
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
selectorReducer,
|
|
5
|
+
type SelectorState,
|
|
6
|
+
} from "../reducers/selectorReducer.js";
|
|
4
7
|
|
|
5
8
|
export interface ModelSelectorProps {
|
|
6
9
|
onCancel: () => void;
|
|
@@ -17,29 +20,29 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
|
17
20
|
configuredModels,
|
|
18
21
|
onSelectModel,
|
|
19
22
|
}) => {
|
|
20
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
23
|
+
const [state, dispatch] = useReducer(selectorReducer<string>, {
|
|
21
24
|
selectedIndex:
|
|
22
25
|
configuredModels.indexOf(currentModel) !== -1
|
|
23
26
|
? configuredModels.indexOf(currentModel)
|
|
24
27
|
: 0,
|
|
25
28
|
pendingDecision: null,
|
|
26
|
-
|
|
29
|
+
items: configuredModels,
|
|
30
|
+
} as SelectorState<string>);
|
|
27
31
|
|
|
28
|
-
const { selectedIndex, pendingDecision } = state;
|
|
32
|
+
const { selectedIndex, pendingDecision, items: models } = state;
|
|
29
33
|
|
|
30
34
|
useInput((_input, key) => {
|
|
31
35
|
dispatch({
|
|
32
36
|
type: "HANDLE_KEY",
|
|
33
37
|
key,
|
|
34
|
-
maxIndex: configuredModels.length - 1,
|
|
35
38
|
hasInsert: false,
|
|
36
39
|
});
|
|
37
40
|
});
|
|
38
41
|
|
|
39
42
|
useEffect(() => {
|
|
40
43
|
if (pendingDecision === "select") {
|
|
41
|
-
if (
|
|
42
|
-
onSelectModel(
|
|
44
|
+
if (models.length > 0) {
|
|
45
|
+
onSelectModel(models[selectedIndex]);
|
|
43
46
|
}
|
|
44
47
|
onCancel();
|
|
45
48
|
dispatch({ type: "CLEAR_DECISION" });
|
|
@@ -47,23 +50,17 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
|
47
50
|
onCancel();
|
|
48
51
|
dispatch({ type: "CLEAR_DECISION" });
|
|
49
52
|
}
|
|
50
|
-
}, [
|
|
51
|
-
pendingDecision,
|
|
52
|
-
selectedIndex,
|
|
53
|
-
configuredModels,
|
|
54
|
-
onSelectModel,
|
|
55
|
-
onCancel,
|
|
56
|
-
]);
|
|
53
|
+
}, [pendingDecision, selectedIndex, models, onSelectModel, onCancel]);
|
|
57
54
|
|
|
58
55
|
// Calculate visible window
|
|
59
56
|
const startIndex = Math.max(
|
|
60
57
|
0,
|
|
61
58
|
Math.min(
|
|
62
59
|
selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2),
|
|
63
|
-
Math.max(0,
|
|
60
|
+
Math.max(0, models.length - MAX_VISIBLE_ITEMS),
|
|
64
61
|
),
|
|
65
62
|
);
|
|
66
|
-
const visibleModels =
|
|
63
|
+
const visibleModels = models.slice(
|
|
67
64
|
startIndex,
|
|
68
65
|
startIndex + MAX_VISIBLE_ITEMS,
|
|
69
66
|
);
|