@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.
Files changed (150) hide show
  1. package/README.ja.md +3 -4
  2. package/README.md +3 -4
  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 +15 -15
  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 -7
  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/cli/ui/utils/modelOptions.d.ts.map +1 -1
  67. package/dist/cli/ui/utils/modelOptions.js +25 -16
  68. package/dist/cli/ui/utils/modelOptions.js.map +1 -1
  69. package/dist/client/assets/{index-f5D2XwDh.js → index-v8smkNOL.js} +16 -16
  70. package/dist/client/index.html +1 -1
  71. package/dist/codex.d.ts +32 -0
  72. package/dist/codex.d.ts.map +1 -1
  73. package/dist/codex.js +32 -1
  74. package/dist/codex.js.map +1 -1
  75. package/dist/config/builtin-tools.d.ts +1 -5
  76. package/dist/config/builtin-tools.d.ts.map +1 -1
  77. package/dist/config/builtin-tools.js +1 -18
  78. package/dist/config/builtin-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 +43 -61
  82. package/dist/gemini.js.map +1 -1
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +0 -20
  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 +36 -27
  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 -10
  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 +9 -6
  130. package/src/cli/ui/utils/modelOptions.ts +25 -18
  131. package/src/codex.ts +40 -1
  132. package/src/config/builtin-tools.ts +1 -19
  133. package/src/gemini.ts +47 -68
  134. package/src/index.ts +0 -26
  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
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,64 +152,43 @@ 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
- // Capture session ID from Gemini's exit summary
157
+ // Preserve TTY for interactive UI (colors/width) by inheriting stdout/stderr.
158
+ // Session ID is determined via file-based detection after exit.
138
159
  let capturedSessionId: string | null = null;
139
- const extractSessionId = (output: string | undefined) => {
140
- if (!output) return;
141
- // Gemini outputs "Session ID: <uuid>" in exit summary
142
- // UUID may be split across lines due to terminal width
143
- // First, find "Session ID:" and extract following hex characters
144
- const sessionIdIndex = output.indexOf("Session ID:");
145
- if (sessionIdIndex === -1) return;
146
160
 
147
- // Extract text after "Session ID:" until we have enough hex chars for UUID
148
- const afterLabel = output.slice(sessionIdIndex + "Session ID:".length);
149
- // Remove all non-hex characters except dash, then extract UUID pattern
150
- const hexOnly = afterLabel.replace(/[^0-9a-fA-F-]/g, "");
151
- // UUID format: 8-4-4-4-12 = 32 hex chars + 4 dashes
152
- const uuidMatch = hexOnly.match(
153
- /^([0-9a-f]{8})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{12})/i,
154
- );
155
- if (uuidMatch) {
156
- capturedSessionId = `${uuidMatch[1]}-${uuidMatch[2]}-${uuidMatch[3]}-${uuidMatch[4]}-${uuidMatch[5]}`;
157
- }
158
- };
159
-
160
- const runGemini = async (
161
- runArgs: string[],
162
- ): Promise<string | undefined> => {
163
- // Capture stdout while passing through to terminal
164
- // Store chunks to extract session ID after process exits
165
- const outputChunks: string[] = [];
161
+ const runGemini = async (runArgs: string[]): Promise<void> => {
162
+ const execChild = async (child: Promise<unknown>) => {
163
+ try {
164
+ await child;
165
+ } catch (execError: unknown) {
166
+ // Treat SIGINT/SIGTERM as normal exit (user pressed Ctrl+C)
167
+ const signal = (execError as { signal?: unknown })?.signal;
168
+ if (signal === "SIGINT" || signal === "SIGTERM") {
169
+ return;
170
+ }
171
+ throw execError;
172
+ }
173
+ };
166
174
 
167
- const runWithCapture = async (cmd: string, args: string[]) => {
175
+ const run = async (cmd: string, args: string[]) => {
168
176
  const child = execa(cmd, args, {
169
177
  cwd: worktreePath,
170
178
  shell: true,
171
179
  stdin: childStdio.stdin,
172
- stdout: "pipe",
180
+ stdout: childStdio.stdout,
173
181
  stderr: childStdio.stderr,
174
182
  env: baseEnv,
175
183
  });
176
-
177
- // Pass stdout through to terminal while capturing
178
- child.stdout?.on("data", (chunk: Buffer) => {
179
- const text = chunk.toString("utf8");
180
- outputChunks.push(text);
181
- terminal.stdout.write(chunk);
182
- });
183
-
184
- await child;
185
- return outputChunks.join("");
184
+ await execChild(child);
186
185
  };
187
186
 
188
187
  if (hasLocalGemini) {
189
188
  console.log(
190
189
  chalk.green(" ✨ Using locally installed gemini command"),
191
190
  );
192
- return await runWithCapture("gemini", runArgs);
191
+ return await run("gemini", runArgs);
193
192
  }
194
193
  console.log(
195
194
  chalk.cyan(" 🔄 Falling back to bunx @google/gemini-cli@latest"),
@@ -202,15 +201,14 @@ export async function launchGeminiCLI(
202
201
  console.log(chalk.yellow(" npm install -g @google/gemini-cli"));
203
202
  console.log("");
204
203
  await new Promise((resolve) => setTimeout(resolve, 2000));
205
- return await runWithCapture("bunx", [GEMINI_CLI_PACKAGE, ...runArgs]);
204
+ return await run("bunx", [GEMINI_CLI_PACKAGE, ...runArgs]);
206
205
  };
207
206
 
208
- let output: string | undefined;
209
207
  let fellBackToLatest = false;
210
208
  try {
211
209
  // Try with explicit session ID first (if any), then fallback to --resume (latest) once
212
210
  try {
213
- output = await runGemini(argsPrimary);
211
+ await runGemini(argsPrimary);
214
212
  } catch (err) {
215
213
  const shouldRetry =
216
214
  (options.mode === "resume" || options.mode === "continue") &&
@@ -222,7 +220,7 @@ export async function launchGeminiCLI(
222
220
  ` ⚠️ Failed to resume session ${resumeSessionId}. Retrying with latest session...`,
223
221
  ),
224
222
  );
225
- output = await runGemini(argsFallback);
223
+ await runGemini(argsFallback);
226
224
  } else {
227
225
  throw err;
228
226
  }
@@ -231,9 +229,6 @@ export async function launchGeminiCLI(
231
229
  childStdio.cleanup();
232
230
  }
233
231
 
234
- // Extract session ID from Gemini's exit summary output
235
- extractSessionId(output);
236
-
237
232
  const explicitResumeSucceeded = usedExplicitSessionId && !fellBackToLatest;
238
233
 
239
234
  // If we explicitly resumed a specific session (and did not fall back), keep that ID.
@@ -268,7 +263,7 @@ export async function launchGeminiCLI(
268
263
 
269
264
  return capturedSessionId ? { sessionId: capturedSessionId } : {};
270
265
  } catch (error: unknown) {
271
- const hasLocalGemini = await isGeminiCommandAvailable();
266
+ const hasLocalGemini = await isCommandAvailable("gemini");
272
267
  let errorMessage: string;
273
268
  const err = error as NodeJS.ErrnoException;
274
269
 
@@ -316,29 +311,13 @@ export async function launchGeminiCLI(
316
311
  throw new GeminiError(errorMessage, error);
317
312
  } finally {
318
313
  terminal.exitRawMode();
314
+ resetTerminalModes(terminal.stdout);
319
315
  }
320
316
  }
321
317
 
322
318
  /**
323
- * Check if locally installed `gemini` command is available
324
- * @returns true if `gemini` command exists in PATH, false otherwise
319
+ * Checks whether Gemini CLI is available via `bunx` in the current environment.
325
320
  */
326
- async function isGeminiCommandAvailable(): Promise<boolean> {
327
- try {
328
- const command = process.platform === "win32" ? "where" : "which";
329
- await execa(command, ["gemini"], {
330
- shell: true,
331
- stdin: "ignore",
332
- stdout: "ignore",
333
- stderr: "ignore",
334
- });
335
- return true;
336
- } catch {
337
- // gemini command not found in PATH
338
- return false;
339
- }
340
- }
341
-
342
321
  export async function isGeminiCLIAvailable(): Promise<boolean> {
343
322
  try {
344
323
  await execa("bunx", [GEMINI_CLI_PACKAGE, "--version"], { shell: true });
package/src/index.ts CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  type CodexReasoningEffort,
17
17
  } from "./codex.js";
18
18
  import { launchGeminiCLI, GeminiError } from "./gemini.js";
19
- import { launchQwenCLI, QwenError } from "./qwen.js";
20
19
  import {
21
20
  WorktreeOrchestrator,
22
21
  type EnsureWorktreeOptions,
@@ -117,7 +116,6 @@ function isRecoverableError(error: unknown): boolean {
117
116
  error instanceof WorktreeError ||
118
117
  error instanceof CodexError ||
119
118
  error instanceof GeminiError ||
120
- error instanceof QwenError ||
121
119
  error instanceof DependencyInstallError
122
120
  ) {
123
121
  return true;
@@ -129,7 +127,6 @@ function isRecoverableError(error: unknown): boolean {
129
127
  error.name === "WorktreeError" ||
130
128
  error.name === "CodexError" ||
131
129
  error.name === "GeminiError" ||
132
- error.name === "QwenError" ||
133
130
  error.name === "DependencyInstallError"
134
131
  );
135
132
  }
@@ -144,7 +141,6 @@ function isRecoverableError(error: unknown): boolean {
144
141
  name === "WorktreeError" ||
145
142
  name === "CodexError" ||
146
143
  name === "GeminiError" ||
147
- name === "QwenError" ||
148
144
  name === "DependencyInstallError"
149
145
  );
150
146
  }
@@ -658,28 +654,6 @@ export async function handleAIToolWorkflow(
658
654
  launchOptions.model = model;
659
655
  }
660
656
  launchResult = await launchGeminiCLI(worktreePath, launchOptions);
661
- } else if (tool === "qwen-cli") {
662
- const launchOptions: {
663
- mode?: "normal" | "continue" | "resume";
664
- skipPermissions?: boolean;
665
- envOverrides?: Record<string, string>;
666
- model?: string;
667
- sessionId?: string | null;
668
- } = {
669
- mode:
670
- mode === "resume"
671
- ? "resume"
672
- : mode === "continue"
673
- ? "continue"
674
- : "normal",
675
- skipPermissions,
676
- envOverrides: sharedEnv,
677
- sessionId: resumeSessionId,
678
- };
679
- if (model) {
680
- launchOptions.model = model;
681
- }
682
- launchResult = await launchQwenCLI(worktreePath, launchOptions);
683
657
  } else {
684
658
  // Custom tool
685
659
  printInfo(`Launching custom tool: ${toolConfig.displayName}`);
@@ -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"}