@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/claude.ts CHANGED
@@ -1,10 +1,20 @@
1
- import { execa } from "execa";
1
+ import { execa, type Options as ExecaOptions } 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 { findLatestClaudeSession } from "./utils/session.js";
6
11
 
7
12
  const CLAUDE_CLI_PACKAGE = "@anthropic-ai/claude-code@latest";
13
+
14
+ /**
15
+ * Error wrapper used by `launchClaudeCode` to preserve the original failure
16
+ * while providing a user-friendly message.
17
+ */
8
18
  export class ClaudeError extends Error {
9
19
  constructor(
10
20
  message: string,
@@ -15,6 +25,20 @@ export class ClaudeError extends Error {
15
25
  }
16
26
  }
17
27
 
28
+ /**
29
+ * Launches Claude Code in the given worktree path.
30
+ *
31
+ * This function:
32
+ * - validates the worktree path
33
+ * - normalizes launch arguments (mode/model/session/extra args)
34
+ * - resets terminal modes before and after the child process
35
+ * - auto-detects a local `claude` command and falls back to `npx` (Windows) or
36
+ * `bunx` when needed
37
+ *
38
+ * @param worktreePath - Worktree directory to run Claude Code in
39
+ * @param options - Launch options (mode/session/model/permissions/env)
40
+ * @returns Captured session id when available
41
+ */
18
42
  export async function launchClaudeCode(
19
43
  worktreePath: string,
20
44
  options: {
@@ -175,6 +199,7 @@ export async function launchClaudeCode(
175
199
  console.log(chalk.gray(` 📋 Args: ${args.join(" ")}`));
176
200
 
177
201
  terminal.exitRawMode();
202
+ resetTerminalModes(terminal.stdout);
178
203
 
179
204
  const baseEnv = { ...process.env, ...(options.envOverrides ?? {}) };
180
205
  const launchEnvSource =
@@ -191,26 +216,58 @@ export async function launchClaudeCode(
191
216
 
192
217
  // Auto-detect locally installed claude command
193
218
  const hasLocalClaude = await isClaudeCommandAvailable();
219
+ const hasNpx =
220
+ process.platform === "win32" ? await isNpxCommandAvailable() : false;
221
+
222
+ const execInteractive = async (
223
+ file: string,
224
+ fileArgs: string[],
225
+ execOptions: Omit<ExecaOptions, "shell">,
226
+ ) => {
227
+ if (process.platform !== "win32") {
228
+ await execa(file, fileArgs, { ...execOptions, shell: true });
229
+ return;
230
+ }
231
+
232
+ try {
233
+ await execa(file, fileArgs, { ...execOptions, shell: false });
234
+ return;
235
+ } catch (error: unknown) {
236
+ const err = error as NodeJS.ErrnoException;
237
+ if (err?.code === "ENOENT" || err?.code === "EINVAL") {
238
+ await execa(file, fileArgs, { ...execOptions, shell: true });
239
+ return;
240
+ }
241
+ throw error;
242
+ }
243
+ };
194
244
 
195
245
  try {
196
246
  if (hasLocalClaude) {
197
247
  console.log(
198
248
  chalk.green(" ✨ Using locally installed claude command"),
199
249
  );
200
- await execa("claude", args, {
250
+ await execInteractive("claude", args, {
201
251
  cwd: worktreePath,
202
- shell: true,
203
252
  stdin: childStdio.stdin,
204
253
  stdout: childStdio.stdout,
205
254
  stderr: childStdio.stderr,
206
255
  env: launchEnv,
207
256
  });
208
257
  } else {
209
- console.log(
210
- chalk.cyan(
211
- " 🔄 Falling back to bunx @anthropic-ai/claude-code@latest",
212
- ),
213
- );
258
+ if (hasNpx) {
259
+ console.log(
260
+ chalk.cyan(
261
+ " 🔄 Falling back to npx @anthropic-ai/claude-code@latest",
262
+ ),
263
+ );
264
+ } else {
265
+ console.log(
266
+ chalk.cyan(
267
+ " 🔄 Falling back to bunx @anthropic-ai/claude-code@latest",
268
+ ),
269
+ );
270
+ }
214
271
  console.log(
215
272
  chalk.yellow(
216
273
  " 💡 Recommended: Install Claude Code via official method for faster startup",
@@ -231,14 +288,23 @@ export async function launchClaudeCode(
231
288
  );
232
289
  console.log("");
233
290
  await new Promise((resolve) => setTimeout(resolve, 2000));
234
- await execa("bunx", [CLAUDE_CLI_PACKAGE, ...args], {
235
- cwd: worktreePath,
236
- shell: true,
237
- stdin: childStdio.stdin,
238
- stdout: childStdio.stdout,
239
- stderr: childStdio.stderr,
240
- env: launchEnv,
241
- });
291
+ if (hasNpx) {
292
+ await execInteractive("npx", ["-y", CLAUDE_CLI_PACKAGE, ...args], {
293
+ cwd: worktreePath,
294
+ stdin: childStdio.stdin,
295
+ stdout: childStdio.stdout,
296
+ stderr: childStdio.stderr,
297
+ env: launchEnv,
298
+ });
299
+ } else {
300
+ await execInteractive("bunx", [CLAUDE_CLI_PACKAGE, ...args], {
301
+ cwd: worktreePath,
302
+ stdin: childStdio.stdin,
303
+ stdout: childStdio.stdout,
304
+ stderr: childStdio.stderr,
305
+ env: launchEnv,
306
+ });
307
+ }
242
308
  }
243
309
  } finally {
244
310
  childStdio.cleanup();
@@ -327,29 +393,21 @@ export async function launchClaudeCode(
327
393
  throw new ClaudeError(errorMessage, error);
328
394
  } finally {
329
395
  terminal.exitRawMode();
396
+ resetTerminalModes(terminal.stdout);
330
397
  }
331
398
  }
332
399
 
333
- /**
334
- * Check if locally installed `claude` command is available
335
- * @returns true if `claude` command exists in PATH, false otherwise
336
- */
337
400
  async function isClaudeCommandAvailable(): Promise<boolean> {
338
- try {
339
- const command = process.platform === "win32" ? "where" : "which";
340
- await execa(command, ["claude"], {
341
- shell: true,
342
- stdin: "ignore",
343
- stdout: "ignore",
344
- stderr: "ignore",
345
- });
346
- return true;
347
- } catch {
348
- // claude command not found in PATH
349
- return false;
350
- }
401
+ return isCommandAvailable("claude");
351
402
  }
352
403
 
404
+ async function isNpxCommandAvailable(): Promise<boolean> {
405
+ return isCommandAvailable("npx");
406
+ }
407
+
408
+ /**
409
+ * Checks whether Claude Code is available via `bunx` in the current environment.
410
+ */
353
411
  export async function isClaudeCodeAvailable(): Promise<boolean> {
354
412
  try {
355
413
  await execa("bunx", [CLAUDE_CLI_PACKAGE, "--version"], { shell: true });
@@ -204,6 +204,8 @@ describe("App protected branch handling", () => {
204
204
  });
205
205
 
206
206
  expect(navigateToMock).toHaveBeenCalledWith("branch-action-selector");
207
- expect(branchQuickStartProps).not.toHaveLength(0);
207
+ // When branchQuickStart is empty, navigates to ai-tool-selector instead of branch-quick-start
208
+ // So AIToolSelectorScreen should be rendered, not BranchQuickStartScreen
209
+ expect(aiToolProps).not.toHaveLength(0);
208
210
  });
209
211
  });
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import React from "react";
5
5
  import { describe, it, expect, beforeEach, vi } from "vitest";
6
- import { render, waitFor, fireEvent, screen } from "@testing-library/react";
6
+ import { render, waitFor } from "@testing-library/react";
7
7
  import { ModelSelectorScreen } from "../../components/screens/ModelSelectorScreen.js";
8
8
  import type { ModelSelectionResult } from "../../components/screens/ModelSelectorScreen.js";
9
9
  import { Window } from "happy-dom";
@@ -55,8 +55,9 @@ describe("ModelSelectorScreen initial selection", () => {
55
55
  await waitFor(() => expect(selectMocks.length).toBeGreaterThan(0));
56
56
  const modelSelect = selectMocks.at(-1);
57
57
  const index = modelSelect.initialIndex as number;
58
- // codex-cli models: [gpt-5.1-codex, gpt-5.2, gpt-5.1-codex-max, gpt-5.1-codex-mini, gpt-5.1]
59
- expect(index).toBe(2);
58
+ // codex-cli models: ["", gpt-5.1-codex, gpt-5.2, gpt-5.1-codex-max, gpt-5.1-codex-mini, gpt-5.1]
59
+ // (index 0 = Default/Auto, index 3 = gpt-5.1-codex-max)
60
+ expect(index).toBe(3);
60
61
  });
61
62
 
62
63
  it("includes gpt-5.2 in model options and preserves ordering", async () => {
@@ -75,9 +76,11 @@ describe("ModelSelectorScreen initial selection", () => {
75
76
  );
76
77
 
77
78
  await waitFor(() => expect(selectMocks.length).toBeGreaterThan(1));
78
- const modelSelect = selectMocks.at(-1)!;
79
- const ids = modelSelect.items.map((item) => item.value);
79
+ const modelSelect = selectMocks.at(-1);
80
+ expect(modelSelect).toBeDefined();
81
+ const ids = modelSelect?.items.map((item) => item.value);
80
82
  expect(ids).toEqual([
83
+ "", // Default (Auto)
81
84
  "gpt-5.1-codex",
82
85
  "gpt-5.2",
83
86
  "gpt-5.1-codex-max",
@@ -1,11 +1,18 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import { Box, Text, useInput, useStdout } from "ink";
2
+ import { Box, Text, useStdout } from "ink";
3
+ import { useAppInput } from "../../hooks/useAppInput.js";
3
4
 
5
+ /**
6
+ * Item descriptor for the `Select` component.
7
+ */
4
8
  export interface SelectItem {
5
9
  label: string;
6
10
  value: string;
7
11
  }
8
12
 
13
+ /**
14
+ * Props for the `Select` component.
15
+ */
9
16
  export interface SelectProps<T extends SelectItem = SelectItem> {
10
17
  items: T[];
11
18
  onSelect: (item: T) => void;
@@ -133,7 +140,7 @@ const SelectComponent = <T extends SelectItem = SelectItem>({
133
140
  }
134
141
  }, [items, limit]);
135
142
 
136
- useInput((input, key) => {
143
+ useAppInput((input, key) => {
137
144
  if (disabled) {
138
145
  return;
139
146
  }
@@ -1,19 +1,26 @@
1
1
  import React, { useState, useEffect } from "react";
2
- import { Box, Text, useInput } from "ink";
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 { getAllTools } from "../../../../config/tools.js";
8
9
  import type { AIToolConfig } from "../../../../types/tools.js";
9
10
  import type { AITool } from "../../types.js";
10
11
 
12
+ /**
13
+ * Renderable item for the AI tool selector list.
14
+ */
11
15
  export interface AIToolItem {
12
16
  label: string;
13
17
  value: AITool;
14
18
  description: string;
15
19
  }
16
20
 
21
+ /**
22
+ * Props for `AIToolSelectorScreen`.
23
+ */
17
24
  export interface AIToolSelectorScreenProps {
18
25
  onBack: () => void;
19
26
  onSelect: (tool: AITool) => void;
@@ -96,7 +103,7 @@ export function AIToolSelectorScreen({
96
103
 
97
104
  // Handle keyboard input
98
105
  // Note: Select component handles Enter and arrow keys
99
- useInput((input, key) => {
106
+ useAppInput((input, key) => {
100
107
  if (key.escape) {
101
108
  onBack();
102
109
  }
@@ -1,12 +1,16 @@
1
1
  import React from "react";
2
- import { Box, Text, useInput } from "ink";
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 { ProgressBar } from "../parts/ProgressBar.js";
6
6
  import { MergeStatusList } from "../parts/MergeStatusList.js";
7
+ import { useAppInput } from "../../hooks/useAppInput.js";
7
8
  import { useTerminalSize } from "../../hooks/useTerminalSize.js";
8
9
  import type { BatchMergeProgress, BranchMergeStatus } from "../../types.js";
9
10
 
11
+ /**
12
+ * Props for `BatchMergeProgressScreen`.
13
+ */
10
14
  export interface BatchMergeProgressScreenProps {
11
15
  progress: BatchMergeProgress;
12
16
  statuses: BranchMergeStatus[];
@@ -26,7 +30,7 @@ export function BatchMergeProgressScreen({
26
30
  const { rows } = useTerminalSize();
27
31
 
28
32
  // Handle keyboard input
29
- useInput((input, key) => {
33
+ useAppInput((input, key) => {
30
34
  if ((input === "q" || key.escape) && onCancel) {
31
35
  onCancel();
32
36
  }
@@ -1,11 +1,15 @@
1
1
  import React from "react";
2
- import { Box, Text, useInput } from "ink";
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 { MergeStatusList } from "../parts/MergeStatusList.js";
6
+ import { useAppInput } from "../../hooks/useAppInput.js";
6
7
  import { useTerminalSize } from "../../hooks/useTerminalSize.js";
7
8
  import type { BatchMergeResult } from "../../types.js";
8
9
 
10
+ /**
11
+ * Props for `BatchMergeResultScreen`.
12
+ */
9
13
  export interface BatchMergeResultScreenProps {
10
14
  result: BatchMergeResult;
11
15
  onBack?: () => void;
@@ -25,7 +29,7 @@ export function BatchMergeResultScreen({
25
29
  const { rows } = useTerminalSize();
26
30
 
27
31
  // Handle keyboard input
28
- useInput((input, key) => {
32
+ useAppInput((input, key) => {
29
33
  if (input === "q" && onQuit) {
30
34
  onQuit();
31
35
  } else if (key.escape && onBack) {
@@ -1,9 +1,10 @@
1
1
  import React, { useState, useCallback, useEffect, useRef } from "react";
2
- import { Box, Text, useInput } from "ink";
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
6
  import { Input } from "../common/Input.js";
7
+ import { useAppInput } from "../../hooks/useAppInput.js";
7
8
  import { useTerminalSize } from "../../hooks/useTerminalSize.js";
8
9
  import { BRANCH_PREFIXES } from "../../../../config/constants.js";
9
10
 
@@ -12,6 +13,9 @@ type Step = "type-selection" | "name-input";
12
13
 
13
14
  const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧"];
14
15
 
16
+ /**
17
+ * Props for `BranchCreatorScreen`.
18
+ */
15
19
  export interface BranchCreatorScreenProps {
16
20
  onBack: () => void;
17
21
  onCreate: (branchName: string) => Promise<void>;
@@ -52,7 +56,7 @@ export function BranchCreatorScreen({
52
56
  const spinnerFrame = SPINNER_FRAMES[spinnerIndex] ?? SPINNER_FRAMES[0];
53
57
 
54
58
  // Handle keyboard input for back navigation
55
- useInput((input, key) => {
59
+ useAppInput((input, key) => {
56
60
  if (isCreating) {
57
61
  return;
58
62
  }
@@ -1,11 +1,12 @@
1
1
  import React, { useCallback, useState, useMemo, useEffect } from "react";
2
- import { Box, Text, useInput } from "ink";
2
+ import { Box, Text } from "ink";
3
3
  import { Header } from "../parts/Header.js";
4
4
  import { Stats } from "../parts/Stats.js";
5
5
  import { Footer } from "../parts/Footer.js";
6
6
  import { Select } from "../common/Select.js";
7
7
  import { Input } from "../common/Input.js";
8
8
  import { LoadingIndicator } from "../common/LoadingIndicator.js";
9
+ import { useAppInput } from "../../hooks/useAppInput.js";
9
10
  import { useTerminalSize } from "../../hooks/useTerminalSize.js";
10
11
  import type { BranchItem, Statistics } from "../../types.js";
11
12
  import stringWidth from "string-width";
@@ -77,6 +78,9 @@ interface CleanupUIState {
77
78
  inputLocked: boolean;
78
79
  }
79
80
 
81
+ /**
82
+ * Props for `BranchListScreen`.
83
+ */
80
84
  export interface BranchListScreenProps {
81
85
  branches: BranchItem[];
82
86
  stats: Statistics;
@@ -166,7 +170,7 @@ export function BranchListScreen({
166
170
  // Handle keyboard input
167
171
  // Note: Input component blocks specific keys (c/r/f) using blockKeys prop
168
172
  // This prevents shortcuts from triggering while typing in the filter
169
- useInput((input, key) => {
173
+ useAppInput((input, key) => {
170
174
  if (cleanupUI?.inputLocked) {
171
175
  return;
172
176
  }
@@ -326,8 +330,6 @@ export function BranchListScreen({
326
330
  return chalk.cyan(label);
327
331
  case "gemini-cli":
328
332
  return chalk.magenta(label);
329
- case "qwen-cli":
330
- return chalk.green(label);
331
333
  default: {
332
334
  const trimmed = label.trim().toLowerCase();
333
335
  if (!toolId || trimmed === "unknown") {
@@ -1,16 +1,23 @@
1
1
  import React from "react";
2
- import { Box, Text, useInput } from "ink";
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
 
9
+ /**
10
+ * Action returned by `BranchQuickStartScreen`.
11
+ */
8
12
  export type QuickStartAction = "reuse-continue" | "reuse-new" | "manual";
9
13
 
14
+ /**
15
+ * Previous tool/session configuration shown in the quick start list.
16
+ */
10
17
  export interface BranchQuickStartOption {
11
18
  toolId?: string | null;
12
19
  toolLabel: string;
13
- toolCategory?: "Codex" | "Claude" | "Gemini" | "Qwen" | "Other";
20
+ toolCategory?: "Codex" | "Claude" | "Gemini" | "Other";
14
21
  model?: string | null;
15
22
  sessionId?: string | null;
16
23
  inferenceLevel?: string | null;
@@ -54,6 +61,9 @@ type QuickStartItem = SelectItem & {
54
61
  categoryColor: "cyan" | "yellow" | "magenta" | "green" | "white";
55
62
  };
56
63
 
64
+ /**
65
+ * Props for `BranchQuickStartScreen`.
66
+ */
57
67
  export interface BranchQuickStartScreenProps {
58
68
  previousOptions: BranchQuickStartOption[];
59
69
  loading?: boolean;
@@ -78,7 +88,6 @@ export function BranchQuickStartScreen({
78
88
  "codex-cli": { label: "Codex", color: "cyan" },
79
89
  "claude-code": { label: "Claude", color: "yellow" },
80
90
  "gemini-cli": { label: "Gemini", color: "magenta" },
81
- "qwen-cli": { label: "Qwen", color: "green" },
82
91
  other: { label: "Other", color: "white" },
83
92
  } as const;
84
93
 
@@ -92,8 +101,6 @@ export function BranchQuickStartScreen({
92
101
  return CATEGORY_META["claude-code"];
93
102
  case "gemini-cli":
94
103
  return CATEGORY_META["gemini-cli"];
95
- case "qwen-cli":
96
- return CATEGORY_META["qwen-cli"];
97
104
  default:
98
105
  return CATEGORY_META.other;
99
106
  }
@@ -101,7 +108,7 @@ export function BranchQuickStartScreen({
101
108
 
102
109
  const items: QuickStartItem[] = previousOptions.length
103
110
  ? (() => {
104
- const order = ["Claude", "Codex", "Gemini", "Qwen", "Other"];
111
+ const order = ["Claude", "Codex", "Gemini", "Other"];
105
112
  const sorted = [...previousOptions].sort((a, b) => {
106
113
  const ca = resolveCategory(a.toolId).label;
107
114
  const cb = resolveCategory(b.toolId).label;
@@ -171,7 +178,7 @@ export function BranchQuickStartScreen({
171
178
  categoryColor: CATEGORY_META.other.color,
172
179
  });
173
180
 
174
- useInput((_, key) => {
181
+ useAppInput((_, key) => {
175
182
  if (key.escape) {
176
183
  onBack();
177
184
  }
@@ -197,27 +204,29 @@ export function BranchQuickStartScreen({
197
204
  if (item.disabled) return;
198
205
  onSelect(item.action, item.toolId ?? null);
199
206
  }}
200
- renderItem={(item: QuickStartItem, isSelected) => (
201
- <Box
202
- flexDirection="column"
203
- marginTop={
204
- item.groupStart ? 1 : item.category === "Other" ? 1 : 0
205
- }
206
- >
207
- <Text>
208
- <Text color={item.categoryColor} inverse={isSelected}>
209
- {`[${item.category}] `}
207
+ renderItem={(item: QuickStartItem, isSelected) => {
208
+ return (
209
+ <Box
210
+ flexDirection="column"
211
+ marginTop={
212
+ item.groupStart ? 1 : item.category === "Other" ? 1 : 0
213
+ }
214
+ >
215
+ <Text>
216
+ <Text color={item.categoryColor} inverse={isSelected}>
217
+ {`[${item.category}] `}
218
+ </Text>
219
+ <Text inverse={isSelected}>
220
+ {item.label}
221
+ {item.disabled ? " (disabled)" : ""}
222
+ </Text>
210
223
  </Text>
211
- <Text inverse={isSelected}>
212
- {item.label}
213
- {item.disabled ? " (disabled)" : ""}
214
- </Text>
215
- </Text>
216
- {item.description && (
217
- <Text color="gray"> {item.description}</Text>
218
- )}
219
- </Box>
220
- )}
224
+ {item.description && (
225
+ <Text color="gray"> {item.description}</Text>
226
+ )}
227
+ </Box>
228
+ );
229
+ }}
221
230
  />
222
231
  </Box>
223
232
 
@@ -6,16 +6,20 @@
6
6
  */
7
7
 
8
8
  import React, { useState, useCallback, useMemo, useEffect } from "react";
9
- import { Box, Text, useInput } from "ink";
9
+ import { Box, Text } from "ink";
10
10
  import { Header } from "../parts/Header.js";
11
11
  import { Footer } from "../parts/Footer.js";
12
12
  import { Select } from "../common/Select.js";
13
13
  import { Input } from "../common/Input.js";
14
14
  import { Confirm } from "../common/Confirm.js";
15
+ import { useAppInput } from "../../hooks/useAppInput.js";
15
16
  import { useTerminalSize } from "../../hooks/useTerminalSize.js";
16
17
  import { useProfiles } from "../../hooks/useProfiles.js";
17
18
  import { isValidProfileName } from "../../../../types/profiles.js";
18
19
 
20
+ /**
21
+ * Props for `EnvironmentProfileScreen`.
22
+ */
19
23
  export interface EnvironmentProfileScreenProps {
20
24
  onBack: () => void;
21
25
  version?: string | null;
@@ -432,7 +436,7 @@ export function EnvironmentProfileScreen({
432
436
  );
433
437
 
434
438
  // キーボード入力ハンドリング
435
- useInput(
439
+ useAppInput(
436
440
  (input, key) => {
437
441
  // 入力モード時は他のキーハンドリングをスキップ
438
442
  if (
@@ -1,29 +1,45 @@
1
1
  import React, { useState } from "react";
2
- import { Box, Text, useInput } from "ink";
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
+ * Supported execution modes for interactive tools.
11
+ */
8
12
  export type ExecutionMode = "normal" | "continue" | "resume";
9
13
 
14
+ /**
15
+ * Selectable execution mode item (step 1).
16
+ */
10
17
  export interface ExecutionModeItem {
11
18
  label: string;
12
19
  value: ExecutionMode;
13
20
  description: string;
14
21
  }
15
22
 
23
+ /**
24
+ * Selectable skip-permissions item (step 2).
25
+ */
16
26
  export interface SkipPermissionsItem {
17
27
  label: string;
18
28
  value: string; // "yes" or "no"
19
29
  description: string;
20
30
  }
21
31
 
32
+ /**
33
+ * Result returned by `ExecutionModeSelectorScreen`.
34
+ */
22
35
  export interface ExecutionModeResult {
23
36
  mode: ExecutionMode;
24
37
  skipPermissions: boolean;
25
38
  }
26
39
 
40
+ /**
41
+ * Props for `ExecutionModeSelectorScreen`.
42
+ */
27
43
  export interface ExecutionModeSelectorScreenProps {
28
44
  onBack: () => void;
29
45
  onSelect: (result: ExecutionModeResult) => void;
@@ -48,7 +64,7 @@ export function ExecutionModeSelectorScreen({
48
64
  const [selectedMode, setSelectedMode] = useState<ExecutionMode | null>(null);
49
65
 
50
66
  // Handle keyboard input
51
- useInput((input, key) => {
67
+ useAppInput((input, key) => {
52
68
  if (key.escape) {
53
69
  if (step === 2) {
54
70
  // Go back to step 1