@akiojin/gwt 3.1.2 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +3 -4
- package/README.md +3 -4
- 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 +15 -15
- 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 -7
- 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 +25 -16
- 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 +32 -0
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +32 -1
- package/dist/codex.js.map +1 -1
- package/dist/config/builtin-tools.d.ts +1 -5
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +1 -18
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/gemini.d.ts +17 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +43 -61
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -20
- package/dist/index.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 +8 -5
- 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 +36 -27
- 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 -10
- 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 +9 -6
- package/src/cli/ui/utils/modelOptions.ts +25 -18
- package/src/codex.ts +40 -1
- package/src/config/builtin-tools.ts +1 -19
- package/src/gemini.ts +47 -68
- package/src/index.ts +0 -26
- 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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from "react";
|
|
2
|
-
import { Box, Text
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
import { Header } from "../parts/Header.js";
|
|
4
4
|
import { Footer } from "../parts/Footer.js";
|
|
5
5
|
import { Select, type SelectItem } from "../common/Select.js";
|
|
6
|
+
import { useAppInput } from "../../hooks/useAppInput.js";
|
|
6
7
|
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
8
|
import type { AITool, InferenceLevel, ModelOption } from "../../types.js";
|
|
8
9
|
import {
|
|
@@ -12,6 +13,9 @@ import {
|
|
|
12
13
|
getModelOptions,
|
|
13
14
|
} from "../../utils/modelOptions.js";
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Result returned by `ModelSelectorScreen`.
|
|
18
|
+
*/
|
|
15
19
|
export interface ModelSelectionResult {
|
|
16
20
|
model: string | null;
|
|
17
21
|
inferenceLevel?: InferenceLevel;
|
|
@@ -25,6 +29,9 @@ interface InferenceSelectItem extends SelectItem {
|
|
|
25
29
|
hint?: string;
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Props for `ModelSelectorScreen`.
|
|
34
|
+
*/
|
|
28
35
|
export interface ModelSelectorScreenProps {
|
|
29
36
|
tool: AITool;
|
|
30
37
|
onBack: () => void;
|
|
@@ -37,7 +44,6 @@ const TOOL_LABELS: Record<string, string> = {
|
|
|
37
44
|
"claude-code": "Claude Code",
|
|
38
45
|
"codex-cli": "Codex",
|
|
39
46
|
"gemini-cli": "Gemini",
|
|
40
|
-
"qwen-cli": "Qwen",
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
const INFERENCE_LABELS: Record<InferenceLevel, string> = {
|
|
@@ -159,7 +165,7 @@ export function ModelSelectorScreen({
|
|
|
159
165
|
return index >= 0 ? index : 0;
|
|
160
166
|
}, [initialSelection?.inferenceLevel, inferenceOptions, selectedModel]);
|
|
161
167
|
|
|
162
|
-
|
|
168
|
+
useAppInput((_input, key) => {
|
|
163
169
|
if (key.escape) {
|
|
164
170
|
if (step === "inference") {
|
|
165
171
|
setStep("model");
|
|
@@ -255,13 +261,6 @@ export function ModelSelectorScreen({
|
|
|
255
261
|
{modelOptions.length === 0 ? " (no options)" : ""}
|
|
256
262
|
</Text>
|
|
257
263
|
</Box>
|
|
258
|
-
{tool === "qwen-cli" ? (
|
|
259
|
-
<Box marginBottom={1} flexDirection="column">
|
|
260
|
-
<Text>Latest Qwen models from Alibaba Cloud ModelStudio:</Text>
|
|
261
|
-
<Text>• coder-model (qwen3-coder-plus-2025-09-23)</Text>
|
|
262
|
-
<Text>• vision-model (qwen3-vl-plus-2025-09-23)</Text>
|
|
263
|
-
</Box>
|
|
264
|
-
) : null}
|
|
265
264
|
|
|
266
265
|
{modelItems.length === 0 ? (
|
|
267
266
|
<Select
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Text
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
import { Header } from "../parts/Header.js";
|
|
4
4
|
import { Footer } from "../parts/Footer.js";
|
|
5
5
|
import { Select } from "../common/Select.js";
|
|
6
|
+
import { useAppInput } from "../../hooks/useAppInput.js";
|
|
6
7
|
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
8
|
import type { CleanupTarget } from "../../types.js";
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Renderable item for the cleanup target list.
|
|
12
|
+
*/
|
|
9
13
|
export interface PRItem {
|
|
10
14
|
label: string;
|
|
11
15
|
value: string;
|
|
12
16
|
target: CleanupTarget;
|
|
13
17
|
}
|
|
14
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Props for `PRCleanupScreen`.
|
|
21
|
+
*/
|
|
15
22
|
export interface PRCleanupScreenProps {
|
|
16
23
|
targets: CleanupTarget[];
|
|
17
24
|
loading: boolean;
|
|
@@ -41,7 +48,7 @@ export function PRCleanupScreen({
|
|
|
41
48
|
|
|
42
49
|
// Handle keyboard input
|
|
43
50
|
// Note: Select component handles Enter and arrow keys
|
|
44
|
-
|
|
51
|
+
useAppInput((input, key) => {
|
|
45
52
|
if (key.escape) {
|
|
46
53
|
onBack();
|
|
47
54
|
} else if (input === "r") {
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Text
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
import { Header } from "../parts/Header.js";
|
|
4
4
|
import { Footer } from "../parts/Footer.js";
|
|
5
5
|
import { Select } from "../common/Select.js";
|
|
6
|
+
import { useAppInput } from "../../hooks/useAppInput.js";
|
|
6
7
|
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Renderable item for the session selector list.
|
|
11
|
+
*/
|
|
8
12
|
export interface SessionItem {
|
|
9
13
|
label: string;
|
|
10
14
|
value: string;
|
|
11
15
|
secondary?: string;
|
|
12
16
|
}
|
|
13
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Props for `SessionSelectorScreen`.
|
|
20
|
+
*/
|
|
14
21
|
export interface SessionSelectorScreenProps {
|
|
15
22
|
sessions: {
|
|
16
23
|
sessionId: string;
|
|
@@ -41,7 +48,7 @@ export function SessionSelectorScreen({
|
|
|
41
48
|
|
|
42
49
|
// Handle keyboard input
|
|
43
50
|
// Note: Select component handles Enter and arrow keys
|
|
44
|
-
|
|
51
|
+
useAppInput((input, key) => {
|
|
45
52
|
if (key.escape) {
|
|
46
53
|
onBack();
|
|
47
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
|
}
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
const byId = (tool: string) => getModelOptions(tool).map((m) => m.id);
|
|
9
9
|
|
|
10
10
|
describe("modelOptions", () => {
|
|
11
|
-
it("lists Claude official aliases and sets
|
|
11
|
+
it("lists Claude official aliases and sets Default as default", () => {
|
|
12
12
|
const options = getModelOptions("claude-code");
|
|
13
13
|
const ids = options.map((m) => m.id);
|
|
14
|
-
expect(ids).toEqual(["opus", "sonnet", "haiku"]);
|
|
14
|
+
expect(ids).toEqual(["", "opus", "sonnet", "haiku"]);
|
|
15
15
|
const defaultModel = getDefaultModelOption("claude-code");
|
|
16
|
-
expect(defaultModel?.id).toBe("
|
|
17
|
-
expect(defaultModel?.label).toBe("
|
|
16
|
+
expect(defaultModel?.id).toBe("");
|
|
17
|
+
expect(defaultModel?.label).toBe("Default (Auto)");
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
it("has unique Codex models", () => {
|
|
@@ -22,6 +22,7 @@ describe("modelOptions", () => {
|
|
|
22
22
|
const unique = new Set(ids);
|
|
23
23
|
expect(unique.size).toBe(ids.length);
|
|
24
24
|
expect(ids).toEqual([
|
|
25
|
+
"",
|
|
25
26
|
"gpt-5.1-codex",
|
|
26
27
|
"gpt-5.2",
|
|
27
28
|
"gpt-5.1-codex-max",
|
|
@@ -52,14 +53,16 @@ describe("modelOptions", () => {
|
|
|
52
53
|
|
|
53
54
|
it("lists expected Gemini models", () => {
|
|
54
55
|
expect(byId("gemini-cli")).toEqual([
|
|
56
|
+
"",
|
|
55
57
|
"gemini-3-pro-preview",
|
|
58
|
+
"gemini-3-flash-preview",
|
|
56
59
|
"gemini-2.5-pro",
|
|
57
60
|
"gemini-2.5-flash",
|
|
58
61
|
"gemini-2.5-flash-lite",
|
|
59
62
|
]);
|
|
60
63
|
});
|
|
61
64
|
|
|
62
|
-
it("
|
|
63
|
-
expect(byId("
|
|
65
|
+
it("returns no models for unsupported tools", () => {
|
|
66
|
+
expect(byId("unknown-tool")).toEqual([]);
|
|
64
67
|
});
|
|
65
68
|
});
|
|
@@ -5,12 +5,17 @@ const CODEX_MAX_LEVELS: InferenceLevel[] = ["xhigh", "high", "medium", "low"];
|
|
|
5
5
|
|
|
6
6
|
const MODEL_OPTIONS: Record<string, ModelOption[]> = {
|
|
7
7
|
"claude-code": [
|
|
8
|
+
{
|
|
9
|
+
id: "",
|
|
10
|
+
label: "Default (Auto)",
|
|
11
|
+
description: "Use Claude Code default behavior",
|
|
12
|
+
isDefault: true,
|
|
13
|
+
},
|
|
8
14
|
{
|
|
9
15
|
id: "opus",
|
|
10
16
|
label: "Opus 4.5",
|
|
11
17
|
description:
|
|
12
18
|
"Official Opus alias for Claude Code (non-custom, matches /model option).",
|
|
13
|
-
isDefault: true,
|
|
14
19
|
},
|
|
15
20
|
{
|
|
16
21
|
id: "sonnet",
|
|
@@ -25,13 +30,20 @@ const MODEL_OPTIONS: Record<string, ModelOption[]> = {
|
|
|
25
30
|
},
|
|
26
31
|
],
|
|
27
32
|
"codex-cli": [
|
|
33
|
+
{
|
|
34
|
+
id: "",
|
|
35
|
+
label: "Default (Auto)",
|
|
36
|
+
description: "Use Codex default model",
|
|
37
|
+
isDefault: true,
|
|
38
|
+
inferenceLevels: CODEX_BASE_LEVELS,
|
|
39
|
+
defaultInference: "high",
|
|
40
|
+
},
|
|
28
41
|
{
|
|
29
42
|
id: "gpt-5.1-codex",
|
|
30
43
|
label: "gpt-5.1-codex",
|
|
31
44
|
description: "Standard Codex model",
|
|
32
45
|
inferenceLevels: CODEX_BASE_LEVELS,
|
|
33
46
|
defaultInference: "high",
|
|
34
|
-
isDefault: true,
|
|
35
47
|
},
|
|
36
48
|
{
|
|
37
49
|
id: "gpt-5.2",
|
|
@@ -63,12 +75,22 @@ const MODEL_OPTIONS: Record<string, ModelOption[]> = {
|
|
|
63
75
|
},
|
|
64
76
|
],
|
|
65
77
|
"gemini-cli": [
|
|
78
|
+
{
|
|
79
|
+
id: "",
|
|
80
|
+
label: "Default (Auto)",
|
|
81
|
+
description: "Use Gemini CLI default model",
|
|
82
|
+
isDefault: true,
|
|
83
|
+
},
|
|
66
84
|
{
|
|
67
85
|
id: "gemini-3-pro-preview",
|
|
68
86
|
label: "Pro (gemini-3-pro-preview)",
|
|
69
87
|
description:
|
|
70
88
|
"Default Pro. Falls back to gemini-2.5-pro when preview is unavailable.",
|
|
71
|
-
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "gemini-3-flash-preview",
|
|
92
|
+
label: "Flash (gemini-3-flash-preview)",
|
|
93
|
+
description: "Next-generation high-speed model",
|
|
72
94
|
},
|
|
73
95
|
{
|
|
74
96
|
id: "gemini-2.5-pro",
|
|
@@ -86,21 +108,6 @@ const MODEL_OPTIONS: Record<string, ModelOption[]> = {
|
|
|
86
108
|
description: "Fastest for simple tasks",
|
|
87
109
|
},
|
|
88
110
|
],
|
|
89
|
-
"qwen-cli": [
|
|
90
|
-
{
|
|
91
|
-
id: "coder-model",
|
|
92
|
-
label: "Coder Model",
|
|
93
|
-
description:
|
|
94
|
-
"Latest Qwen Coder model (qwen3-coder-plus-2025-09-23) from Alibaba Cloud ModelStudio",
|
|
95
|
-
isDefault: true,
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
id: "vision-model",
|
|
99
|
-
label: "Vision Model",
|
|
100
|
-
description:
|
|
101
|
-
"Latest Qwen Vision model (qwen3-vl-plus-2025-09-23) from Alibaba Cloud ModelStudio",
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
111
|
};
|
|
105
112
|
|
|
106
113
|
export function getModelOptions(tool: AITool): ModelOption[] {
|
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
|
|
|
19
|
+
/**
|
|
20
|
+
* Default Codex model used when no override is provided.
|
|
21
|
+
*/
|
|
12
22
|
export const DEFAULT_CODEX_MODEL = "gpt-5.1-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,23 +58,6 @@ export const GEMINI_CLI_TOOL: CustomAITool = {
|
|
|
58
58
|
permissionSkipArgs: ["-y"],
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* Qwen のビルトイン定義
|
|
63
|
-
*/
|
|
64
|
-
export const QWEN_CLI_TOOL: CustomAITool = {
|
|
65
|
-
id: "qwen-cli",
|
|
66
|
-
displayName: "Qwen",
|
|
67
|
-
type: "bunx",
|
|
68
|
-
command: "@qwen-code/qwen-code@latest",
|
|
69
|
-
defaultArgs: ["--checkpointing"],
|
|
70
|
-
modeArgs: {
|
|
71
|
-
normal: [],
|
|
72
|
-
continue: [],
|
|
73
|
-
resume: [],
|
|
74
|
-
},
|
|
75
|
-
permissionSkipArgs: ["--yolo"],
|
|
76
|
-
};
|
|
77
|
-
|
|
78
61
|
/**
|
|
79
62
|
* すべてのビルトインツール
|
|
80
63
|
*/
|
|
@@ -82,5 +65,4 @@ export const BUILTIN_TOOLS: CustomAITool[] = [
|
|
|
82
65
|
CLAUDE_CODE_TOOL,
|
|
83
66
|
CODEX_CLI_TOOL,
|
|
84
67
|
GEMINI_CLI_TOOL,
|
|
85
|
-
QWEN_CLI_TOOL,
|
|
86
68
|
];
|