@akiojin/gwt 4.0.0 → 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.
Files changed (150) hide show
  1. package/README.ja.md +0 -1
  2. package/README.md +0 -1
  3. package/dist/claude.d.ts +21 -0
  4. package/dist/claude.d.ts.map +1 -1
  5. package/dist/claude.js +73 -30
  6. package/dist/claude.js.map +1 -1
  7. package/dist/cli/ui/components/common/Select.d.ts +6 -0
  8. package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
  9. package/dist/cli/ui/components/common/Select.js +3 -2
  10. package/dist/cli/ui/components/common/Select.js.map +1 -1
  11. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +6 -0
  12. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
  13. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +3 -2
  14. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
  15. package/dist/cli/ui/components/screens/BatchMergeProgressScreen.d.ts +3 -0
  16. package/dist/cli/ui/components/screens/BatchMergeProgressScreen.d.ts.map +1 -1
  17. package/dist/cli/ui/components/screens/BatchMergeProgressScreen.js +3 -2
  18. package/dist/cli/ui/components/screens/BatchMergeProgressScreen.js.map +1 -1
  19. package/dist/cli/ui/components/screens/BatchMergeResultScreen.d.ts +3 -0
  20. package/dist/cli/ui/components/screens/BatchMergeResultScreen.d.ts.map +1 -1
  21. package/dist/cli/ui/components/screens/BatchMergeResultScreen.js +3 -2
  22. package/dist/cli/ui/components/screens/BatchMergeResultScreen.js.map +1 -1
  23. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +3 -0
  24. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
  25. package/dist/cli/ui/components/screens/BranchCreatorScreen.js +3 -2
  26. package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
  27. package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -0
  28. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  29. package/dist/cli/ui/components/screens/BranchListScreen.js +3 -4
  30. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  31. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts +10 -1
  32. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
  33. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +7 -22
  34. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
  35. package/dist/cli/ui/components/screens/EnvironmentProfileScreen.d.ts +3 -0
  36. package/dist/cli/ui/components/screens/EnvironmentProfileScreen.d.ts.map +1 -1
  37. package/dist/cli/ui/components/screens/EnvironmentProfileScreen.js +3 -2
  38. package/dist/cli/ui/components/screens/EnvironmentProfileScreen.js.map +1 -1
  39. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +15 -0
  40. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
  41. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +3 -2
  42. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
  43. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +6 -0
  44. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -1
  45. package/dist/cli/ui/components/screens/ModelSelectorScreen.js +3 -2
  46. package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -1
  47. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +6 -0
  48. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts.map +1 -1
  49. package/dist/cli/ui/components/screens/PRCleanupScreen.js +3 -2
  50. package/dist/cli/ui/components/screens/PRCleanupScreen.js.map +1 -1
  51. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +6 -0
  52. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts.map +1 -1
  53. package/dist/cli/ui/components/screens/SessionSelectorScreen.js +3 -2
  54. package/dist/cli/ui/components/screens/SessionSelectorScreen.js.map +1 -1
  55. package/dist/cli/ui/hooks/useAppInput.d.ts +20 -0
  56. package/dist/cli/ui/hooks/useAppInput.d.ts.map +1 -0
  57. package/dist/cli/ui/hooks/useAppInput.js +137 -0
  58. package/dist/cli/ui/hooks/useAppInput.js.map +1 -0
  59. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts +3 -0
  60. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
  61. package/dist/cli/ui/screens/BranchActionSelectorScreen.js +3 -2
  62. package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
  63. package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
  64. package/dist/cli/ui/utils/branchFormatter.js +0 -2
  65. package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
  66. package/dist/client/assets/{index-f5D2XwDh.js → index-v8smkNOL.js} +16 -16
  67. package/dist/client/index.html +1 -1
  68. package/dist/codex.d.ts +32 -0
  69. package/dist/codex.d.ts.map +1 -1
  70. package/dist/codex.js +32 -1
  71. package/dist/codex.js.map +1 -1
  72. package/dist/config/builtin-tools.d.ts +1 -7
  73. package/dist/config/builtin-tools.d.ts.map +1 -1
  74. package/dist/config/builtin-tools.js +1 -20
  75. package/dist/config/builtin-tools.js.map +1 -1
  76. package/dist/config/tools.d.ts.map +1 -1
  77. package/dist/config/tools.js +1 -4
  78. package/dist/config/tools.js.map +1 -1
  79. package/dist/gemini.d.ts +17 -0
  80. package/dist/gemini.d.ts.map +1 -1
  81. package/dist/gemini.js +21 -21
  82. package/dist/gemini.js.map +1 -1
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +0 -5
  85. package/dist/index.js.map +1 -1
  86. package/dist/utils/command.d.ts +10 -0
  87. package/dist/utils/command.d.ts.map +1 -0
  88. package/dist/utils/command.js +25 -0
  89. package/dist/utils/command.js.map +1 -0
  90. package/dist/utils/session/index.d.ts +0 -2
  91. package/dist/utils/session/index.d.ts.map +1 -1
  92. package/dist/utils/session/index.js +0 -3
  93. package/dist/utils/session/index.js.map +1 -1
  94. package/dist/utils/session/parsers/index.d.ts +0 -1
  95. package/dist/utils/session/parsers/index.d.ts.map +1 -1
  96. package/dist/utils/session/parsers/index.js +0 -2
  97. package/dist/utils/session/parsers/index.js.map +1 -1
  98. package/dist/utils/session.d.ts +0 -1
  99. package/dist/utils/session.d.ts.map +1 -1
  100. package/dist/utils/session.js +0 -1
  101. package/dist/utils/session.js.map +1 -1
  102. package/dist/utils/terminal.d.ts +34 -0
  103. package/dist/utils/terminal.d.ts.map +1 -1
  104. package/dist/utils/terminal.js +51 -4
  105. package/dist/utils/terminal.js.map +1 -1
  106. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -1
  107. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +0 -2
  108. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -1
  109. package/package.json +1 -1
  110. package/src/claude.ts +92 -34
  111. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +3 -1
  112. package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +8 -5
  113. package/src/cli/ui/components/common/Select.tsx +9 -2
  114. package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +9 -2
  115. package/src/cli/ui/components/screens/BatchMergeProgressScreen.tsx +6 -2
  116. package/src/cli/ui/components/screens/BatchMergeResultScreen.tsx +6 -2
  117. package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +6 -2
  118. package/src/cli/ui/components/screens/BranchListScreen.tsx +6 -4
  119. package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +17 -26
  120. package/src/cli/ui/components/screens/EnvironmentProfileScreen.tsx +6 -2
  121. package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +18 -2
  122. package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +9 -2
  123. package/src/cli/ui/components/screens/PRCleanupScreen.tsx +9 -2
  124. package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +9 -2
  125. package/src/cli/ui/hooks/useAppInput.ts +171 -0
  126. package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +6 -2
  127. package/src/cli/ui/screens/__tests__/BranchActionSelectorScreen.test.tsx +68 -1
  128. package/src/cli/ui/utils/branchFormatter.ts +0 -1
  129. package/src/cli/ui/utils/modelOptions.test.ts +1 -1
  130. package/src/codex.ts +40 -1
  131. package/src/config/builtin-tools.ts +1 -21
  132. package/src/config/tools.ts +1 -5
  133. package/src/gemini.ts +25 -21
  134. package/src/index.ts +0 -6
  135. package/src/utils/command.ts +26 -0
  136. package/src/utils/session/index.ts +0 -4
  137. package/src/utils/session/parsers/index.ts +0 -3
  138. package/src/utils/session.ts +0 -1
  139. package/src/utils/terminal.ts +65 -4
  140. package/src/web/client/src/components/branch-detail/BranchInfoCards.tsx +0 -1
  141. package/dist/qwen.d.ts +0 -16
  142. package/dist/qwen.d.ts.map +0 -1
  143. package/dist/qwen.js +0 -202
  144. package/dist/qwen.js.map +0 -1
  145. package/dist/utils/session/parsers/qwen.d.ts +0 -21
  146. package/dist/utils/session/parsers/qwen.d.ts.map +0 -1
  147. package/dist/utils/session/parsers/qwen.js +0 -36
  148. package/dist/utils/session/parsers/qwen.js.map +0 -1
  149. package/src/qwen.ts +0 -273
  150. package/src/utils/session/parsers/qwen.ts +0 -54
@@ -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
  }
@@ -63,6 +63,6 @@ describe("modelOptions", () => {
63
63
  });
64
64
 
65
65
  it("returns no models for unsupported tools", () => {
66
- expect(byId("qwen-cli")).toEqual([]);
66
+ expect(byId("unknown-tool")).toEqual([]);
67
67
  });
68
68
  });
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 { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
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、Qwen の CustomAITool 形式定義
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
  ];
@@ -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
- })).filter((tool) => !UNSUPPORTED_BUILTIN_TOOL_IDS.has(tool.id));
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 { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
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 isGeminiCommandAvailable();
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 isGeminiCommandAvailable();
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
- * Check if locally installed `gemini` command is available
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";
@@ -23,6 +23,3 @@ export {
23
23
  findLatestGeminiSession,
24
24
  findLatestGeminiSessionId,
25
25
  } from "./gemini.js";
26
-
27
- // Qwen CLI
28
- export { findLatestQwenSessionId } from "./qwen.js";
@@ -11,7 +11,6 @@
11
11
  * - ./session/parsers/claude.js - Claude-specific functions
12
12
  * - ./session/parsers/codex.js - Codex-specific functions
13
13
  * - ./session/parsers/gemini.js - Gemini-specific functions
14
- * - ./session/parsers/qwen.js - Qwen-specific functions
15
14
  */
16
15
 
17
16
  export * from "./session/index.js";
@@ -2,6 +2,13 @@ import fs from "node:fs";
2
2
  import { platform } from "node:os";
3
3
  import { ReadStream, WriteStream } from "node:tty";
4
4
 
5
+ /**
6
+ * Terminal streams used by the CLI (stdin/stdout/stderr) and their raw-mode
7
+ * teardown helper.
8
+ *
9
+ * When the current process is not a TTY, this may fall back to `/dev/tty` so
10
+ * interactive child processes can still read/write correctly.
11
+ */
5
12
  export interface TerminalStreams {
6
13
  stdin: NodeJS.ReadStream;
7
14
  stdout: NodeJS.WriteStream;
@@ -14,9 +21,18 @@ export interface TerminalStreams {
14
21
  }
15
22
 
16
23
  const DEV_TTY_PATH = "/dev/tty";
24
+ // 端末モードのリセット:
25
+ // - ESC[?1l: application cursor keys (DECCKM) を無効化
26
+ // - ESC>: keypad mode を normal/numeric に戻す
27
+ // WSL2/Windowsの矢印キー入力が壊れるケースを抑止する。
28
+ const TERMINAL_RESET_SEQUENCE = "\u001b[?1l\u001b>";
17
29
 
18
30
  let cachedStreams: TerminalStreams | null = null;
19
31
 
32
+ /**
33
+ * Stdio configuration for launching an interactive child process (via `execa`),
34
+ * plus a cleanup hook to be called after the child exits.
35
+ */
20
36
  export interface ChildStdio {
21
37
  stdin: "inherit" | { file: string; append?: boolean };
22
38
  stdout: "inherit" | { file: string; append?: boolean };
@@ -165,6 +181,9 @@ function createTerminalStreams(): TerminalStreams {
165
181
  }
166
182
  }
167
183
 
184
+ /**
185
+ * Returns cached terminal streams and falls back to `/dev/tty` when needed.
186
+ */
168
187
  export function getTerminalStreams(): TerminalStreams {
169
188
  if (!cachedStreams) {
170
189
  cachedStreams = createTerminalStreams();
@@ -172,6 +191,35 @@ export function getTerminalStreams(): TerminalStreams {
172
191
  return cachedStreams;
173
192
  }
174
193
 
194
+ /**
195
+ * Best-effort terminal mode reset for interactive sessions.
196
+ *
197
+ * This writes a small ANSI sequence to restore cursor-key/keypad modes, which
198
+ * helps prevent broken arrow-key behavior on some Windows/WSL2 terminals after
199
+ * interactive CLIs exit.
200
+ */
201
+ export function resetTerminalModes(
202
+ stdout: NodeJS.WriteStream | undefined,
203
+ ): void {
204
+ if (!stdout || typeof stdout.write !== "function") {
205
+ return;
206
+ }
207
+ if (!("isTTY" in stdout) || !stdout.isTTY) {
208
+ return;
209
+ }
210
+ try {
211
+ stdout.write(TERMINAL_RESET_SEQUENCE);
212
+ } catch {
213
+ // Ignore terminal reset errors.
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Creates stdio settings for launching a child process.
219
+ *
220
+ * When terminal streams are backed by `/dev/tty`, forwards those file
221
+ * descriptors to the child so it remains interactive.
222
+ */
175
223
  export function createChildStdio(): ChildStdio {
176
224
  const terminal = getTerminalStreams();
177
225
 
@@ -196,6 +244,12 @@ function isInteractive(stream: NodeJS.ReadStream): boolean {
196
244
  return Boolean(stream.isTTY);
197
245
  }
198
246
 
247
+ /**
248
+ * Prints a message and waits for the user to press Enter when running in an
249
+ * interactive terminal.
250
+ *
251
+ * Useful for pausing on errors while ensuring raw mode is disabled.
252
+ */
199
253
  export async function waitForUserAcknowledgement(
200
254
  message: string = DEFAULT_ACK_MESSAGE,
201
255
  ): Promise<void> {
@@ -214,20 +268,26 @@ export async function waitForUserAcknowledgement(
214
268
  terminal.exitRawMode();
215
269
 
216
270
  await new Promise<void>((resolve) => {
217
- const cleanup = () => {
271
+ let finished = false;
272
+
273
+ function cleanup(): void {
274
+ if (finished) {
275
+ return;
276
+ }
277
+ finished = true;
218
278
  stdin.removeListener("data", onData);
219
279
  if (typeof stdin.pause === "function") {
220
280
  stdin.pause();
221
281
  }
222
- };
282
+ }
223
283
 
224
- const onData = (chunk: Buffer | string) => {
284
+ function onData(chunk: Buffer | string): void {
225
285
  const data = typeof chunk === "string" ? chunk : chunk.toString("utf8");
226
286
  if (data.includes("\n") || data.includes("\r")) {
227
287
  cleanup();
228
288
  resolve();
229
289
  }
230
- };
290
+ }
231
291
 
232
292
  if (typeof stdout?.write === "function") {
233
293
  stdout.write(`\n${message}\n`);
@@ -238,5 +298,6 @@ export async function waitForUserAcknowledgement(
238
298
  }
239
299
 
240
300
  stdin.on("data", onData);
301
+ process.once("exit", cleanup);
241
302
  });
242
303
  }
@@ -13,7 +13,6 @@ function mapToolLabel(toolId: string, toolLabel?: string | null): string {
13
13
  if (toolId === "claude-code") return "Claude";
14
14
  if (toolId === "codex-cli") return "Codex";
15
15
  if (toolId === "gemini-cli") return "Gemini";
16
- if (toolId === "qwen-cli") return "Qwen";
17
16
  if (toolLabel) return toolLabel;
18
17
  return "Custom";
19
18
  }
package/dist/qwen.d.ts DELETED
@@ -1,16 +0,0 @@
1
- export declare class QwenError extends Error {
2
- cause?: unknown | undefined;
3
- constructor(message: string, cause?: unknown | undefined);
4
- }
5
- export declare function launchQwenCLI(worktreePath: string, options?: {
6
- skipPermissions?: boolean;
7
- mode?: "normal" | "continue" | "resume";
8
- extraArgs?: string[];
9
- envOverrides?: Record<string, string>;
10
- model?: string;
11
- sessionId?: string | null;
12
- }): Promise<{
13
- sessionId?: string | null;
14
- }>;
15
- export declare function isQwenCLIAvailable(): Promise<boolean>;
16
- //# sourceMappingURL=qwen.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"qwen.d.ts","sourceRoot":"","sources":["../src/qwen.ts"],"names":[],"mappings":"AAQA,qBAAa,SAAU,SAAQ,KAAK;IAGzB,KAAK,CAAC,EAAE,OAAO;gBADtB,OAAO,EAAE,MAAM,EACR,KAAK,CAAC,EAAE,OAAO,YAAA;CAKzB;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,GACL,OAAO,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAmNxC;AAiBD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAgB3D"}