@akiojin/gwt 4.0.0 → 4.1.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/README.ja.md +0 -1
- package/README.md +0 -1
- package/dist/claude.d.ts +21 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +73 -30
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +6 -0
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +3 -2
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +6 -0
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +3 -2
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BatchMergeProgressScreen.d.ts +3 -0
- package/dist/cli/ui/components/screens/BatchMergeProgressScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BatchMergeProgressScreen.js +3 -2
- package/dist/cli/ui/components/screens/BatchMergeProgressScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BatchMergeResultScreen.d.ts +3 -0
- package/dist/cli/ui/components/screens/BatchMergeResultScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BatchMergeResultScreen.js +3 -2
- package/dist/cli/ui/components/screens/BatchMergeResultScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +3 -0
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js +3 -2
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -0
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +3 -4
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts +10 -1
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +7 -22
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/EnvironmentProfileScreen.d.ts +3 -0
- package/dist/cli/ui/components/screens/EnvironmentProfileScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/EnvironmentProfileScreen.js +3 -2
- package/dist/cli/ui/components/screens/EnvironmentProfileScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +15 -0
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +3 -2
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +6 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js +3 -2
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +6 -0
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/PRCleanupScreen.js +3 -2
- package/dist/cli/ui/components/screens/PRCleanupScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +6 -0
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js +3 -2
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js.map +1 -1
- package/dist/cli/ui/hooks/useAppInput.d.ts +20 -0
- package/dist/cli/ui/hooks/useAppInput.d.ts.map +1 -0
- package/dist/cli/ui/hooks/useAppInput.js +137 -0
- package/dist/cli/ui/hooks/useAppInput.js.map +1 -0
- package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts +3 -0
- package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js +3 -2
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +0 -2
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.js +11 -18
- package/dist/cli/ui/utils/modelOptions.js.map +1 -1
- package/dist/client/assets/{index-f5D2XwDh.js → index-v8smkNOL.js} +16 -16
- package/dist/client/index.html +1 -1
- package/dist/codex.d.ts +33 -1
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +33 -2
- package/dist/codex.js.map +1 -1
- package/dist/config/builtin-tools.d.ts +1 -7
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +1 -20
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +1 -4
- package/dist/config/tools.js.map +1 -1
- package/dist/gemini.d.ts +17 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +21 -21
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -5
- package/dist/index.js.map +1 -1
- package/dist/shared/aiToolConstants.d.ts +1 -1
- package/dist/shared/aiToolConstants.d.ts.map +1 -1
- package/dist/shared/aiToolConstants.js +1 -1
- package/dist/shared/aiToolConstants.js.map +1 -1
- package/dist/utils/command.d.ts +10 -0
- package/dist/utils/command.d.ts.map +1 -0
- package/dist/utils/command.js +25 -0
- package/dist/utils/command.js.map +1 -0
- package/dist/utils/session/index.d.ts +0 -2
- package/dist/utils/session/index.d.ts.map +1 -1
- package/dist/utils/session/index.js +0 -3
- package/dist/utils/session/index.js.map +1 -1
- package/dist/utils/session/parsers/index.d.ts +0 -1
- package/dist/utils/session/parsers/index.d.ts.map +1 -1
- package/dist/utils/session/parsers/index.js +0 -2
- package/dist/utils/session/parsers/index.js.map +1 -1
- package/dist/utils/session.d.ts +0 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +0 -1
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/terminal.d.ts +34 -0
- package/dist/utils/terminal.d.ts.map +1 -1
- package/dist/utils/terminal.js +51 -4
- package/dist/utils/terminal.js.map +1 -1
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -1
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +0 -2
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -1
- package/package.json +1 -1
- package/src/claude.ts +92 -34
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +3 -1
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +10 -8
- package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +6 -6
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +1 -1
- package/src/cli/ui/components/common/Select.tsx +9 -2
- package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +9 -2
- package/src/cli/ui/components/screens/BatchMergeProgressScreen.tsx +6 -2
- package/src/cli/ui/components/screens/BatchMergeResultScreen.tsx +6 -2
- package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +6 -2
- package/src/cli/ui/components/screens/BranchListScreen.tsx +6 -4
- package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +17 -26
- package/src/cli/ui/components/screens/EnvironmentProfileScreen.tsx +6 -2
- package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +18 -2
- package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +9 -2
- package/src/cli/ui/components/screens/PRCleanupScreen.tsx +9 -2
- package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +9 -2
- package/src/cli/ui/hooks/useAppInput.ts +171 -0
- package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +6 -2
- package/src/cli/ui/screens/__tests__/BranchActionSelectorScreen.test.tsx +68 -1
- package/src/cli/ui/utils/branchFormatter.ts +0 -1
- package/src/cli/ui/utils/modelOptions.test.ts +16 -4
- package/src/cli/ui/utils/modelOptions.ts +12 -18
- package/src/codex.ts +41 -2
- package/src/config/builtin-tools.ts +1 -21
- package/src/config/tools.ts +1 -5
- package/src/gemini.ts +25 -21
- package/src/index.ts +0 -6
- package/src/shared/aiToolConstants.ts +1 -1
- package/src/utils/command.ts +26 -0
- package/src/utils/session/index.ts +0 -4
- package/src/utils/session/parsers/index.ts +0 -3
- package/src/utils/session.ts +0 -1
- package/src/utils/terminal.ts +65 -4
- package/src/web/client/src/components/branch-detail/BranchInfoCards.tsx +0 -1
- package/dist/qwen.d.ts +0 -16
- package/dist/qwen.d.ts.map +0 -1
- package/dist/qwen.js +0 -202
- package/dist/qwen.js.map +0 -1
- package/dist/utils/session/parsers/qwen.d.ts +0 -21
- package/dist/utils/session/parsers/qwen.d.ts.map +0 -1
- package/dist/utils/session/parsers/qwen.js +0 -36
- package/dist/utils/session/parsers/qwen.js.map +0 -1
- package/src/qwen.ts +0 -273
- package/src/utils/session/parsers/qwen.ts +0 -54
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { useInput, type Key } from "ink";
|
|
3
|
+
|
|
4
|
+
const ESCAPE_SEQUENCE_TIMEOUT_MS = 25;
|
|
5
|
+
|
|
6
|
+
type InputHandler = (input: string, key: Key) => void;
|
|
7
|
+
type Options = { isActive?: boolean };
|
|
8
|
+
|
|
9
|
+
type BufferedEvent = { input: string; key: Key };
|
|
10
|
+
|
|
11
|
+
type PendingEscape = {
|
|
12
|
+
escapeEvent: BufferedEvent;
|
|
13
|
+
bufferedEvents: BufferedEvent[];
|
|
14
|
+
timeoutId: ReturnType<typeof setTimeout> | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const createEmptyKey = (): Key => ({
|
|
18
|
+
upArrow: false,
|
|
19
|
+
downArrow: false,
|
|
20
|
+
leftArrow: false,
|
|
21
|
+
rightArrow: false,
|
|
22
|
+
pageDown: false,
|
|
23
|
+
pageUp: false,
|
|
24
|
+
return: false,
|
|
25
|
+
escape: false,
|
|
26
|
+
ctrl: false,
|
|
27
|
+
shift: false,
|
|
28
|
+
tab: false,
|
|
29
|
+
backspace: false,
|
|
30
|
+
delete: false,
|
|
31
|
+
meta: false,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function parseArrowDirection(
|
|
35
|
+
sequence: string,
|
|
36
|
+
): "up" | "down" | "left" | "right" | null {
|
|
37
|
+
if (!sequence) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const first = sequence[0];
|
|
42
|
+
if (first !== "[" && first !== "O") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const last = sequence.at(-1);
|
|
47
|
+
if (!last) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
switch (last.toUpperCase()) {
|
|
52
|
+
case "A":
|
|
53
|
+
return "up";
|
|
54
|
+
case "B":
|
|
55
|
+
return "down";
|
|
56
|
+
case "C":
|
|
57
|
+
return "right";
|
|
58
|
+
case "D":
|
|
59
|
+
return "left";
|
|
60
|
+
default:
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Ink `useInput` wrapper that buffers an initial Escape press to disambiguate
|
|
67
|
+
* between a real Escape key and a split ANSI escape sequence (e.g., arrow keys
|
|
68
|
+
* in WSL2/Windows terminals).
|
|
69
|
+
*
|
|
70
|
+
* When an Escape is received, waits briefly for subsequent characters. If they
|
|
71
|
+
* form an arrow sequence (e.g., `[A`, `[B`, `OA`), emits a synthetic arrow
|
|
72
|
+
* `Key`. Otherwise, forwards the original Escape keypress.
|
|
73
|
+
*
|
|
74
|
+
* @param inputHandler - Callback invoked for each normalized input event
|
|
75
|
+
* @param options - Optional Ink input options (e.g., `{ isActive: false }`)
|
|
76
|
+
*/
|
|
77
|
+
export function useAppInput(
|
|
78
|
+
inputHandler: InputHandler,
|
|
79
|
+
options?: Options,
|
|
80
|
+
): void {
|
|
81
|
+
const handlerRef = useRef(inputHandler);
|
|
82
|
+
const pendingRef = useRef<PendingEscape | null>(null);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
handlerRef.current = inputHandler;
|
|
86
|
+
}, [inputHandler]);
|
|
87
|
+
|
|
88
|
+
const clearPending = useCallback(() => {
|
|
89
|
+
const pending = pendingRef.current;
|
|
90
|
+
if (pending?.timeoutId) {
|
|
91
|
+
clearTimeout(pending.timeoutId);
|
|
92
|
+
}
|
|
93
|
+
pendingRef.current = null;
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const flushPending = useCallback(() => {
|
|
97
|
+
const pending = pendingRef.current;
|
|
98
|
+
if (!pending) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
clearPending();
|
|
103
|
+
handlerRef.current(pending.escapeEvent.input, pending.escapeEvent.key);
|
|
104
|
+
for (const event of pending.bufferedEvents) {
|
|
105
|
+
handlerRef.current(event.input, event.key);
|
|
106
|
+
}
|
|
107
|
+
}, [clearPending]);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (options?.isActive === false) {
|
|
111
|
+
clearPending();
|
|
112
|
+
}
|
|
113
|
+
}, [options?.isActive, clearPending]);
|
|
114
|
+
|
|
115
|
+
useEffect(() => clearPending, [clearPending]);
|
|
116
|
+
|
|
117
|
+
useInput((input, key) => {
|
|
118
|
+
const pending = pendingRef.current;
|
|
119
|
+
|
|
120
|
+
if (!pending) {
|
|
121
|
+
if (key.escape) {
|
|
122
|
+
const timeoutId = setTimeout(flushPending, ESCAPE_SEQUENCE_TIMEOUT_MS);
|
|
123
|
+
pendingRef.current = {
|
|
124
|
+
escapeEvent: { input, key },
|
|
125
|
+
bufferedEvents: [],
|
|
126
|
+
timeoutId,
|
|
127
|
+
};
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
handlerRef.current(input, key);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (pending.timeoutId) {
|
|
136
|
+
clearTimeout(pending.timeoutId);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pending.bufferedEvents.push({ input, key });
|
|
140
|
+
|
|
141
|
+
const [firstEvent] = pending.bufferedEvents;
|
|
142
|
+
const firstInput = firstEvent?.input;
|
|
143
|
+
|
|
144
|
+
if (firstInput && firstInput !== "[" && firstInput !== "O") {
|
|
145
|
+
flushPending();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const sequence = pending.bufferedEvents
|
|
150
|
+
.map((event) => event.input)
|
|
151
|
+
.join("");
|
|
152
|
+
const arrow = parseArrowDirection(sequence);
|
|
153
|
+
if (arrow) {
|
|
154
|
+
clearPending();
|
|
155
|
+
const arrowKey = createEmptyKey();
|
|
156
|
+
if (arrow === "up") {
|
|
157
|
+
arrowKey.upArrow = true;
|
|
158
|
+
} else if (arrow === "down") {
|
|
159
|
+
arrowKey.downArrow = true;
|
|
160
|
+
} else if (arrow === "left") {
|
|
161
|
+
arrowKey.leftArrow = true;
|
|
162
|
+
} else if (arrow === "right") {
|
|
163
|
+
arrowKey.rightArrow = true;
|
|
164
|
+
}
|
|
165
|
+
handlerRef.current("", arrowKey);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pending.timeoutId = setTimeout(flushPending, ESCAPE_SEQUENCE_TIMEOUT_MS);
|
|
170
|
+
}, options);
|
|
171
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Text
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
import { Select, type SelectItem } from "../components/common/Select.js";
|
|
4
4
|
import { Footer } from "../components/parts/Footer.js";
|
|
5
|
+
import { useAppInput } from "../hooks/useAppInput.js";
|
|
5
6
|
import type { BranchAction } from "../types.js";
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Props for `BranchActionSelectorScreen`.
|
|
10
|
+
*/
|
|
7
11
|
export interface BranchActionSelectorScreenProps {
|
|
8
12
|
selectedBranch: string;
|
|
9
13
|
onUseExisting: () => void;
|
|
@@ -35,7 +39,7 @@ export function BranchActionSelectorScreen({
|
|
|
35
39
|
secondaryLabel,
|
|
36
40
|
}: BranchActionSelectorScreenProps) {
|
|
37
41
|
// Handle keyboard input for back navigation
|
|
38
|
-
|
|
42
|
+
useAppInput((input, key) => {
|
|
39
43
|
if (key.escape) {
|
|
40
44
|
onBack();
|
|
41
45
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
4
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
5
|
-
import { render } from "@testing-library/react";
|
|
5
|
+
import { act, render } from "@testing-library/react";
|
|
6
|
+
import { render as inkRender } from "ink-testing-library";
|
|
6
7
|
import React from "react";
|
|
7
8
|
import { BranchActionSelectorScreen } from "../BranchActionSelectorScreen.js";
|
|
8
9
|
import { Window } from "happy-dom";
|
|
@@ -149,4 +150,70 @@ describe("BranchActionSelectorScreen", () => {
|
|
|
149
150
|
// For now, we verify the component structure and callbacks are set up
|
|
150
151
|
expect(onCreateNew).not.toHaveBeenCalled();
|
|
151
152
|
});
|
|
153
|
+
|
|
154
|
+
it("should treat split down-arrow sequence as navigation (WSL2) and not as Escape", () => {
|
|
155
|
+
const onUseExisting = vi.fn();
|
|
156
|
+
const onCreateNew = vi.fn();
|
|
157
|
+
const onBack = vi.fn();
|
|
158
|
+
|
|
159
|
+
const inkApp = inkRender(
|
|
160
|
+
<BranchActionSelectorScreen
|
|
161
|
+
selectedBranch="feature-test"
|
|
162
|
+
onUseExisting={onUseExisting}
|
|
163
|
+
onCreateNew={onCreateNew}
|
|
164
|
+
onBack={onBack}
|
|
165
|
+
/>,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
act(() => {
|
|
169
|
+
inkApp.stdin.write("\u001b");
|
|
170
|
+
inkApp.stdin.write("[");
|
|
171
|
+
inkApp.stdin.write("B");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
act(() => {
|
|
175
|
+
inkApp.stdin.write("\r");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(onBack).not.toHaveBeenCalled();
|
|
179
|
+
expect(onCreateNew).toHaveBeenCalledTimes(1);
|
|
180
|
+
expect(onUseExisting).not.toHaveBeenCalled();
|
|
181
|
+
|
|
182
|
+
inkApp.unmount();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should still handle Escape key as back navigation", () => {
|
|
186
|
+
vi.useFakeTimers();
|
|
187
|
+
let inkApp: ReturnType<typeof inkRender> | undefined;
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const onUseExisting = vi.fn();
|
|
191
|
+
const onCreateNew = vi.fn();
|
|
192
|
+
const onBack = vi.fn();
|
|
193
|
+
|
|
194
|
+
inkApp = inkRender(
|
|
195
|
+
<BranchActionSelectorScreen
|
|
196
|
+
selectedBranch="feature-test"
|
|
197
|
+
onUseExisting={onUseExisting}
|
|
198
|
+
onCreateNew={onCreateNew}
|
|
199
|
+
onBack={onBack}
|
|
200
|
+
/>,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
act(() => {
|
|
204
|
+
inkApp.stdin.write("\u001b");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
act(() => {
|
|
208
|
+
vi.advanceTimersByTime(25);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(onBack).toHaveBeenCalledTimes(1);
|
|
212
|
+
expect(onCreateNew).not.toHaveBeenCalled();
|
|
213
|
+
expect(onUseExisting).not.toHaveBeenCalled();
|
|
214
|
+
} finally {
|
|
215
|
+
inkApp?.unmount();
|
|
216
|
+
vi.useRealTimers();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
152
219
|
});
|
|
@@ -98,7 +98,6 @@ function mapToolLabel(toolId: string, toolLabel?: string): string {
|
|
|
98
98
|
if (toolId === "claude-code") return "Claude";
|
|
99
99
|
if (toolId === "codex-cli") return "Codex";
|
|
100
100
|
if (toolId === "gemini-cli") return "Gemini";
|
|
101
|
-
if (toolId === "qwen-cli") return "Qwen";
|
|
102
101
|
if (toolLabel) return toolLabel;
|
|
103
102
|
return "Custom";
|
|
104
103
|
}
|
|
@@ -23,12 +23,24 @@ describe("modelOptions", () => {
|
|
|
23
23
|
expect(unique.size).toBe(ids.length);
|
|
24
24
|
expect(ids).toEqual([
|
|
25
25
|
"",
|
|
26
|
-
"gpt-5.
|
|
27
|
-
"gpt-5.2",
|
|
26
|
+
"gpt-5.2-codex",
|
|
28
27
|
"gpt-5.1-codex-max",
|
|
29
28
|
"gpt-5.1-codex-mini",
|
|
30
|
-
"gpt-5.
|
|
29
|
+
"gpt-5.2",
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("exposes gpt-5.2-codex with xhigh reasoning and high default", () => {
|
|
34
|
+
const codex52 = getModelOptions("codex-cli").find(
|
|
35
|
+
(m) => m.id === "gpt-5.2-codex",
|
|
36
|
+
);
|
|
37
|
+
expect(codex52?.inferenceLevels).toEqual([
|
|
38
|
+
"xhigh",
|
|
39
|
+
"high",
|
|
40
|
+
"medium",
|
|
41
|
+
"low",
|
|
31
42
|
]);
|
|
43
|
+
expect(getDefaultInferenceForModel(codex52)).toBe("high");
|
|
32
44
|
});
|
|
33
45
|
|
|
34
46
|
it("uses medium as default reasoning for codex-max", () => {
|
|
@@ -63,6 +75,6 @@ describe("modelOptions", () => {
|
|
|
63
75
|
});
|
|
64
76
|
|
|
65
77
|
it("returns no models for unsupported tools", () => {
|
|
66
|
-
expect(byId("
|
|
78
|
+
expect(byId("unknown-tool")).toEqual([]);
|
|
67
79
|
});
|
|
68
80
|
});
|
|
@@ -39,39 +39,33 @@ const MODEL_OPTIONS: Record<string, ModelOption[]> = {
|
|
|
39
39
|
defaultInference: "high",
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
id: "gpt-5.
|
|
43
|
-
label: "gpt-5.
|
|
44
|
-
description: "
|
|
45
|
-
inferenceLevels: CODEX_BASE_LEVELS,
|
|
46
|
-
defaultInference: "high",
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: "gpt-5.2",
|
|
50
|
-
label: "gpt-5.2",
|
|
51
|
-
description: "Latest frontier model with extra high reasoning",
|
|
42
|
+
id: "gpt-5.2-codex",
|
|
43
|
+
label: "gpt-5.2-codex",
|
|
44
|
+
description: "Latest frontier agentic coding model",
|
|
52
45
|
inferenceLevels: CODEX_MAX_LEVELS,
|
|
53
|
-
defaultInference: "
|
|
46
|
+
defaultInference: "high",
|
|
54
47
|
},
|
|
55
48
|
{
|
|
56
49
|
id: "gpt-5.1-codex-max",
|
|
57
50
|
label: "gpt-5.1-codex-max",
|
|
58
|
-
description: "
|
|
51
|
+
description: "Codex-optimized flagship for deep and fast reasoning.",
|
|
59
52
|
inferenceLevels: CODEX_MAX_LEVELS,
|
|
60
53
|
defaultInference: "medium",
|
|
61
54
|
},
|
|
62
55
|
{
|
|
63
56
|
id: "gpt-5.1-codex-mini",
|
|
64
57
|
label: "gpt-5.1-codex-mini",
|
|
65
|
-
description: "
|
|
58
|
+
description: "Optimized for codex. Cheaper, faster, but less capable.",
|
|
66
59
|
inferenceLevels: CODEX_BASE_LEVELS,
|
|
67
60
|
defaultInference: "medium",
|
|
68
61
|
},
|
|
69
62
|
{
|
|
70
|
-
id: "gpt-5.
|
|
71
|
-
label: "gpt-5.
|
|
72
|
-
description:
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
id: "gpt-5.2",
|
|
64
|
+
label: "gpt-5.2",
|
|
65
|
+
description:
|
|
66
|
+
"Latest frontier model with improvements across knowledge, reasoning and coding",
|
|
67
|
+
inferenceLevels: CODEX_MAX_LEVELS,
|
|
68
|
+
defaultInference: "medium",
|
|
75
69
|
},
|
|
76
70
|
],
|
|
77
71
|
"gemini-cli": [
|
package/src/codex.ts
CHANGED
|
@@ -2,16 +2,36 @@ import { execa } from "execa";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { platform } from "os";
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createChildStdio,
|
|
7
|
+
getTerminalStreams,
|
|
8
|
+
resetTerminalModes,
|
|
9
|
+
} from "./utils/terminal.js";
|
|
6
10
|
import { findLatestCodexSession } from "./utils/session.js";
|
|
7
11
|
|
|
8
12
|
const CODEX_CLI_PACKAGE = "@openai/codex@latest";
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Reasoning effort levels supported by Codex CLI.
|
|
16
|
+
*/
|
|
10
17
|
export type CodexReasoningEffort = "low" | "medium" | "high" | "xhigh";
|
|
11
18
|
|
|
12
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Default Codex model used when no override is provided.
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_CODEX_MODEL = "gpt-5.2-codex";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default reasoning effort used when no override is provided.
|
|
26
|
+
*/
|
|
13
27
|
export const DEFAULT_CODEX_REASONING_EFFORT: CodexReasoningEffort = "high";
|
|
14
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Builds the default argument list for Codex CLI launch.
|
|
31
|
+
*
|
|
32
|
+
* @param model - Model name to pass via `--model`
|
|
33
|
+
* @param reasoningEffort - Reasoning effort to pass via config
|
|
34
|
+
*/
|
|
15
35
|
export const buildDefaultCodexArgs = (
|
|
16
36
|
model: string = DEFAULT_CODEX_MODEL,
|
|
17
37
|
reasoningEffort: CodexReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT,
|
|
@@ -37,6 +57,10 @@ export const buildDefaultCodexArgs = (
|
|
|
37
57
|
"shell_environment_policy.experimental_use_profile=true",
|
|
38
58
|
];
|
|
39
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Error wrapper used by `launchCodexCLI` to preserve the original failure
|
|
62
|
+
* while providing a user-friendly message.
|
|
63
|
+
*/
|
|
40
64
|
export class CodexError extends Error {
|
|
41
65
|
constructor(
|
|
42
66
|
message: string,
|
|
@@ -47,6 +71,16 @@ export class CodexError extends Error {
|
|
|
47
71
|
}
|
|
48
72
|
}
|
|
49
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Launches Codex CLI in the given worktree path.
|
|
76
|
+
*
|
|
77
|
+
* This function resets terminal modes before and after the child process and
|
|
78
|
+
* tries to detect a session id after launch (when supported).
|
|
79
|
+
*
|
|
80
|
+
* @param worktreePath - Worktree directory to run Codex CLI in
|
|
81
|
+
* @param options - Launch options (mode/session/model/reasoning/env)
|
|
82
|
+
* @returns Captured session id when available
|
|
83
|
+
*/
|
|
50
84
|
export async function launchCodexCLI(
|
|
51
85
|
worktreePath: string,
|
|
52
86
|
options: {
|
|
@@ -133,6 +167,7 @@ export async function launchCodexCLI(
|
|
|
133
167
|
console.log(chalk.gray(` 📋 Args: ${args.join(" ")}`));
|
|
134
168
|
|
|
135
169
|
terminal.exitRawMode();
|
|
170
|
+
resetTerminalModes(terminal.stdout);
|
|
136
171
|
|
|
137
172
|
const childStdio = createChildStdio();
|
|
138
173
|
|
|
@@ -235,9 +270,13 @@ export async function launchCodexCLI(
|
|
|
235
270
|
throw new CodexError(errorMessage, error);
|
|
236
271
|
} finally {
|
|
237
272
|
terminal.exitRawMode();
|
|
273
|
+
resetTerminalModes(terminal.stdout);
|
|
238
274
|
}
|
|
239
275
|
}
|
|
240
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Checks whether Codex CLI is available via `bunx` in the current environment.
|
|
279
|
+
*/
|
|
241
280
|
export async function isCodexAvailable(): Promise<boolean> {
|
|
242
281
|
try {
|
|
243
282
|
await execa("bunx", [CODEX_CLI_PACKAGE, "--help"]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ビルトインAIツール定義
|
|
3
3
|
*
|
|
4
|
-
* Claude Code、Codex、Gemini
|
|
4
|
+
* Claude Code、Codex、Gemini の CustomAITool 形式定義
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { CustomAITool } from "../types/tools.js";
|
|
@@ -58,25 +58,6 @@ export const GEMINI_CLI_TOOL: CustomAITool = {
|
|
|
58
58
|
permissionSkipArgs: ["-y"],
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* Qwen のビルトイン定義
|
|
63
|
-
*
|
|
64
|
-
* NOTE: 現在は未サポート(選択画面には表示しない)。ID予約のため定義のみ残す。
|
|
65
|
-
*/
|
|
66
|
-
export const QWEN_CLI_TOOL: CustomAITool = {
|
|
67
|
-
id: "qwen-cli",
|
|
68
|
-
displayName: "Qwen",
|
|
69
|
-
type: "bunx",
|
|
70
|
-
command: "@qwen-code/qwen-code@latest",
|
|
71
|
-
defaultArgs: ["--checkpointing"],
|
|
72
|
-
modeArgs: {
|
|
73
|
-
normal: [],
|
|
74
|
-
continue: [],
|
|
75
|
-
resume: [],
|
|
76
|
-
},
|
|
77
|
-
permissionSkipArgs: ["--yolo"],
|
|
78
|
-
};
|
|
79
|
-
|
|
80
61
|
/**
|
|
81
62
|
* すべてのビルトインツール
|
|
82
63
|
*/
|
|
@@ -84,5 +65,4 @@ export const BUILTIN_TOOLS: CustomAITool[] = [
|
|
|
84
65
|
CLAUDE_CODE_TOOL,
|
|
85
66
|
CODEX_CLI_TOOL,
|
|
86
67
|
GEMINI_CLI_TOOL,
|
|
87
|
-
QWEN_CLI_TOOL,
|
|
88
68
|
];
|
package/src/config/tools.ts
CHANGED
|
@@ -299,17 +299,13 @@ export async function getToolById(
|
|
|
299
299
|
export async function getAllTools(): Promise<AIToolConfig[]> {
|
|
300
300
|
const config = await loadToolsConfig();
|
|
301
301
|
|
|
302
|
-
// Builtin tools that are reserved but not exposed in selectors.
|
|
303
|
-
// These IDs remain blocked from customTools to avoid ambiguity.
|
|
304
|
-
const UNSUPPORTED_BUILTIN_TOOL_IDS = new Set<string>(["qwen-cli"]);
|
|
305
|
-
|
|
306
302
|
// ビルトインツールをAIToolConfig形式に変換
|
|
307
303
|
const builtinConfigs: AIToolConfig[] = BUILTIN_TOOLS.map((tool) => ({
|
|
308
304
|
id: tool.id,
|
|
309
305
|
displayName: tool.displayName,
|
|
310
306
|
...(tool.icon ? { icon: tool.icon } : {}),
|
|
311
307
|
isBuiltin: true,
|
|
312
|
-
}))
|
|
308
|
+
}));
|
|
313
309
|
|
|
314
310
|
// カスタムツールをAIToolConfig形式に変換
|
|
315
311
|
const customConfigs: AIToolConfig[] = config.customTools.map((tool) => ({
|
package/src/gemini.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { existsSync } from "fs";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createChildStdio,
|
|
6
|
+
getTerminalStreams,
|
|
7
|
+
resetTerminalModes,
|
|
8
|
+
} from "./utils/terminal.js";
|
|
9
|
+
import { isCommandAvailable } from "./utils/command.js";
|
|
5
10
|
import { findLatestGeminiSessionId } from "./utils/session.js";
|
|
6
11
|
|
|
7
12
|
const GEMINI_CLI_PACKAGE = "@google/gemini-cli@latest";
|
|
8
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Error wrapper used by `launchGeminiCLI` to preserve the original failure
|
|
16
|
+
* while providing a user-friendly message.
|
|
17
|
+
*/
|
|
9
18
|
export class GeminiError extends Error {
|
|
10
19
|
constructor(
|
|
11
20
|
message: string,
|
|
@@ -16,6 +25,16 @@ export class GeminiError extends Error {
|
|
|
16
25
|
}
|
|
17
26
|
}
|
|
18
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Launches Gemini CLI in the given worktree path.
|
|
30
|
+
*
|
|
31
|
+
* This function resets terminal modes before and after the child process and
|
|
32
|
+
* supports continue/resume modes when a session id is available.
|
|
33
|
+
*
|
|
34
|
+
* @param worktreePath - Worktree directory to run Gemini CLI in
|
|
35
|
+
* @param options - Launch options (mode/session/model/permissions/env)
|
|
36
|
+
* @returns Captured session id when available
|
|
37
|
+
*/
|
|
19
38
|
export async function launchGeminiCLI(
|
|
20
39
|
worktreePath: string,
|
|
21
40
|
options: {
|
|
@@ -119,6 +138,7 @@ export async function launchGeminiCLI(
|
|
|
119
138
|
);
|
|
120
139
|
}
|
|
121
140
|
terminal.exitRawMode();
|
|
141
|
+
resetTerminalModes(terminal.stdout);
|
|
122
142
|
|
|
123
143
|
const baseEnv = Object.fromEntries(
|
|
124
144
|
Object.entries({
|
|
@@ -132,7 +152,7 @@ export async function launchGeminiCLI(
|
|
|
132
152
|
const childStdio = createChildStdio();
|
|
133
153
|
|
|
134
154
|
// Auto-detect locally installed gemini command
|
|
135
|
-
const hasLocalGemini = await
|
|
155
|
+
const hasLocalGemini = await isCommandAvailable("gemini");
|
|
136
156
|
|
|
137
157
|
// Preserve TTY for interactive UI (colors/width) by inheriting stdout/stderr.
|
|
138
158
|
// Session ID is determined via file-based detection after exit.
|
|
@@ -243,7 +263,7 @@ export async function launchGeminiCLI(
|
|
|
243
263
|
|
|
244
264
|
return capturedSessionId ? { sessionId: capturedSessionId } : {};
|
|
245
265
|
} catch (error: unknown) {
|
|
246
|
-
const hasLocalGemini = await
|
|
266
|
+
const hasLocalGemini = await isCommandAvailable("gemini");
|
|
247
267
|
let errorMessage: string;
|
|
248
268
|
const err = error as NodeJS.ErrnoException;
|
|
249
269
|
|
|
@@ -291,29 +311,13 @@ export async function launchGeminiCLI(
|
|
|
291
311
|
throw new GeminiError(errorMessage, error);
|
|
292
312
|
} finally {
|
|
293
313
|
terminal.exitRawMode();
|
|
314
|
+
resetTerminalModes(terminal.stdout);
|
|
294
315
|
}
|
|
295
316
|
}
|
|
296
317
|
|
|
297
318
|
/**
|
|
298
|
-
*
|
|
299
|
-
* @returns true if `gemini` command exists in PATH, false otherwise
|
|
319
|
+
* Checks whether Gemini CLI is available via `bunx` in the current environment.
|
|
300
320
|
*/
|
|
301
|
-
async function isGeminiCommandAvailable(): Promise<boolean> {
|
|
302
|
-
try {
|
|
303
|
-
const command = process.platform === "win32" ? "where" : "which";
|
|
304
|
-
await execa(command, ["gemini"], {
|
|
305
|
-
shell: true,
|
|
306
|
-
stdin: "ignore",
|
|
307
|
-
stdout: "ignore",
|
|
308
|
-
stderr: "ignore",
|
|
309
|
-
});
|
|
310
|
-
return true;
|
|
311
|
-
} catch {
|
|
312
|
-
// gemini command not found in PATH
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
321
|
export async function isGeminiCLIAvailable(): Promise<boolean> {
|
|
318
322
|
try {
|
|
319
323
|
await execa("bunx", [GEMINI_CLI_PACKAGE, "--version"], { shell: true });
|
package/src/index.ts
CHANGED
|
@@ -307,12 +307,6 @@ export async function handleAIToolWorkflow(
|
|
|
307
307
|
`Selected: ${branchLabel} with ${tool} (${mode} mode${modelInfo}, skipPermissions: ${skipPermissions})`,
|
|
308
308
|
);
|
|
309
309
|
|
|
310
|
-
if (tool === "qwen-cli") {
|
|
311
|
-
printError("Qwen CLI is currently unsupported.");
|
|
312
|
-
await waitForErrorAcknowledgement();
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
310
|
try {
|
|
317
311
|
// Get repository root
|
|
318
312
|
const repoRootResult = await runGitStep("retrieve repository root", () =>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks whether a command is available in the current PATH.
|
|
5
|
+
*
|
|
6
|
+
* Uses `where` on Windows and `which` on other platforms.
|
|
7
|
+
*
|
|
8
|
+
* @param commandName - Command name to look up (e.g. `claude`, `npx`, `gemini`)
|
|
9
|
+
* @returns true if the command exists in PATH
|
|
10
|
+
*/
|
|
11
|
+
export async function isCommandAvailable(
|
|
12
|
+
commandName: string,
|
|
13
|
+
): Promise<boolean> {
|
|
14
|
+
try {
|
|
15
|
+
const command = process.platform === "win32" ? "where" : "which";
|
|
16
|
+
await execa(command, [commandName], {
|
|
17
|
+
shell: true,
|
|
18
|
+
stdin: "ignore",
|
|
19
|
+
stdout: "ignore",
|
|
20
|
+
stderr: "ignore",
|
|
21
|
+
});
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* - Claude Code
|
|
6
6
|
* - Codex CLI
|
|
7
7
|
* - Gemini CLI
|
|
8
|
-
* - Qwen CLI
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
// Type exports
|
|
@@ -41,6 +40,3 @@ export {
|
|
|
41
40
|
findLatestGeminiSession,
|
|
42
41
|
findLatestGeminiSessionId,
|
|
43
42
|
} from "./parsers/gemini.js";
|
|
44
|
-
|
|
45
|
-
// Qwen CLI parser
|
|
46
|
-
export { findLatestQwenSessionId } from "./parsers/qwen.js";
|