@akiojin/gwt 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.ja.md +6 -4
  2. package/README.md +6 -4
  3. package/dist/claude.d.ts +1 -0
  4. package/dist/claude.d.ts.map +1 -1
  5. package/dist/claude.js +6 -3
  6. package/dist/claude.js.map +1 -1
  7. package/dist/cli/ui/components/App.d.ts +6 -4
  8. package/dist/cli/ui/components/App.d.ts.map +1 -1
  9. package/dist/cli/ui/components/App.js +184 -107
  10. package/dist/cli/ui/components/App.js.map +1 -1
  11. package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
  12. package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
  13. package/dist/cli/ui/components/common/Confirm.js +7 -7
  14. package/dist/cli/ui/components/common/Confirm.js.map +1 -1
  15. package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
  16. package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
  17. package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
  18. package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
  19. package/dist/cli/ui/components/common/Input.d.ts +2 -2
  20. package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
  21. package/dist/cli/ui/components/common/Input.js +4 -4
  22. package/dist/cli/ui/components/common/Input.js.map +1 -1
  23. package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
  24. package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
  25. package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
  26. package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
  27. package/dist/cli/ui/components/common/Select.d.ts +1 -1
  28. package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
  29. package/dist/cli/ui/components/common/Select.js +11 -12
  30. package/dist/cli/ui/components/common/Select.js.map +1 -1
  31. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +3 -3
  32. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
  33. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
  34. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
  35. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
  36. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
  37. package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
  38. package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
  39. package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -3
  40. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  41. package/dist/cli/ui/components/screens/BranchListScreen.js +55 -50
  42. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  43. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
  44. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
  45. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
  46. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
  47. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +18 -0
  48. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -0
  49. package/dist/cli/ui/components/screens/ModelSelectorScreen.js +201 -0
  50. package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -0
  51. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
  52. package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
  53. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
  54. package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
  55. package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
  56. package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
  57. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
  58. package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
  59. package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
  60. package/dist/cli/ui/types.d.ts +11 -1
  61. package/dist/cli/ui/types.d.ts.map +1 -1
  62. package/dist/cli/ui/utils/modelOptions.d.ts +6 -0
  63. package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -0
  64. package/dist/cli/ui/utils/modelOptions.js +111 -0
  65. package/dist/cli/ui/utils/modelOptions.js.map +1 -0
  66. package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
  67. package/dist/client/index.html +1 -1
  68. package/dist/codex.d.ts +6 -0
  69. package/dist/codex.d.ts.map +1 -1
  70. package/dist/codex.js +11 -4
  71. package/dist/codex.js.map +1 -1
  72. package/dist/config/builtin-tools.d.ts +10 -2
  73. package/dist/config/builtin-tools.d.ts.map +1 -1
  74. package/dist/config/builtin-tools.js +40 -4
  75. package/dist/config/builtin-tools.js.map +1 -1
  76. package/dist/config/index.d.ts.map +1 -1
  77. package/dist/config/index.js.map +1 -1
  78. package/dist/config/tools.d.ts.map +1 -1
  79. package/dist/config/tools.js +4 -3
  80. package/dist/config/tools.js.map +1 -1
  81. package/dist/gemini.d.ts +13 -0
  82. package/dist/gemini.d.ts.map +1 -0
  83. package/dist/gemini.js +157 -0
  84. package/dist/gemini.js.map +1 -0
  85. package/dist/git.d.ts.map +1 -1
  86. package/dist/git.js.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +59 -7
  89. package/dist/index.js.map +1 -1
  90. package/dist/qwen.d.ts +13 -0
  91. package/dist/qwen.d.ts.map +1 -0
  92. package/dist/qwen.js +157 -0
  93. package/dist/qwen.js.map +1 -0
  94. package/dist/services/git.service.d.ts.map +1 -1
  95. package/dist/services/git.service.js.map +1 -1
  96. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  97. package/dist/web/client/src/components/BranchGraph.js +1 -1
  98. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  99. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  100. package/dist/web/client/src/components/EnvEditor.js +7 -4
  101. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  102. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  103. package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
  104. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  105. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  106. package/dist/web/client/src/pages/BranchListPage.js +10 -4
  107. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  108. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  109. package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
  110. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  111. package/package.json +2 -1
  112. package/src/claude.ts +8 -3
  113. package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
  114. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
  115. package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
  116. package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
  117. package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +81 -0
  118. package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
  119. package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
  120. package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
  121. package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +63 -43
  122. package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
  123. package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
  124. package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
  125. package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
  126. package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
  127. package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
  128. package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
  129. package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
  130. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +261 -153
  131. package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
  132. package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
  133. package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
  134. package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
  135. package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
  136. package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
  137. package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
  138. package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
  139. package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
  140. package/src/cli/ui/components/App.tsx +317 -150
  141. package/src/cli/ui/components/common/Confirm.tsx +13 -9
  142. package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
  143. package/src/cli/ui/components/common/Input.tsx +12 -4
  144. package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
  145. package/src/cli/ui/components/common/Select.tsx +28 -17
  146. package/src/cli/ui/components/parts/Header.test.tsx +5 -15
  147. package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +20 -15
  148. package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
  149. package/src/cli/ui/components/screens/BranchListScreen.tsx +92 -75
  150. package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
  151. package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +320 -0
  152. package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
  153. package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
  154. package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
  155. package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
  156. package/src/cli/ui/types.ts +21 -1
  157. package/src/cli/ui/utils/modelOptions.test.ts +36 -0
  158. package/src/cli/ui/utils/modelOptions.ts +122 -0
  159. package/src/codex.ts +23 -4
  160. package/src/config/builtin-tools.ts +42 -4
  161. package/src/config/index.ts +2 -12
  162. package/src/config/tools.ts +16 -6
  163. package/src/gemini.ts +207 -0
  164. package/src/git.ts +2 -1
  165. package/src/index.ts +86 -6
  166. package/src/qwen.ts +213 -0
  167. package/src/services/git.service.ts +2 -1
  168. package/src/web/client/src/components/BranchGraph.tsx +3 -2
  169. package/src/web/client/src/components/EnvEditor.tsx +44 -11
  170. package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
  171. package/src/web/client/src/pages/BranchListPage.tsx +37 -13
  172. package/src/web/client/src/pages/ConfigManagementPage.tsx +28 -9
package/src/codex.ts CHANGED
@@ -5,14 +5,23 @@ import { existsSync } from "fs";
5
5
  import { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
6
6
 
7
7
  const CODEX_CLI_PACKAGE = "@openai/codex@latest";
8
- const DEFAULT_CODEX_ARGS = [
8
+
9
+ export type CodexReasoningEffort = "low" | "medium" | "high" | "xhigh";
10
+
11
+ export const DEFAULT_CODEX_MODEL = "gpt-5.1-codex";
12
+ export const DEFAULT_CODEX_REASONING_EFFORT: CodexReasoningEffort = "high";
13
+
14
+ export const buildDefaultCodexArgs = (
15
+ model: string = DEFAULT_CODEX_MODEL,
16
+ reasoningEffort: CodexReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT,
17
+ ): string[] => [
9
18
  "--enable",
10
19
  "web_search_request",
11
- "--model=gpt-5.1-codex",
20
+ `--model=${model}`,
12
21
  "--sandbox",
13
22
  "workspace-write",
14
23
  "-c",
15
- "model_reasoning_effort=high",
24
+ `model_reasoning_effort=${reasoningEffort}`,
16
25
  "-c",
17
26
  "model_reasoning_summaries=detailed",
18
27
  "-c",
@@ -42,6 +51,8 @@ export async function launchCodexCLI(
42
51
  extraArgs?: string[];
43
52
  bypassApprovals?: boolean;
44
53
  envOverrides?: Record<string, string>;
54
+ model?: string;
55
+ reasoningEffort?: CodexReasoningEffort;
45
56
  } = {},
46
57
  ): Promise<void> {
47
58
  const terminal = getTerminalStreams();
@@ -55,6 +66,12 @@ export async function launchCodexCLI(
55
66
  console.log(chalk.gray(` Working directory: ${worktreePath}`));
56
67
 
57
68
  const args: string[] = [];
69
+ const model = options.model ?? DEFAULT_CODEX_MODEL;
70
+ const reasoningEffort =
71
+ options.reasoningEffort ?? DEFAULT_CODEX_REASONING_EFFORT;
72
+
73
+ console.log(chalk.green(` 🎯 Model: ${model}`));
74
+ console.log(chalk.green(` 🧠 Reasoning: ${reasoningEffort}`));
58
75
 
59
76
  switch (options.mode) {
60
77
  case "continue":
@@ -80,7 +97,9 @@ export async function launchCodexCLI(
80
97
  args.push(...options.extraArgs);
81
98
  }
82
99
 
83
- args.push(...DEFAULT_CODEX_ARGS);
100
+ const codexArgs = buildDefaultCodexArgs(model, reasoningEffort);
101
+
102
+ args.push(...codexArgs);
84
103
 
85
104
  terminal.exitRawMode();
86
105
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ビルトインAIツール定義
3
3
  *
4
- * Claude CodeCodex CLI の CustomAITool 形式定義
4
+ * Claude CodeCodex、Gemini、Qwen の CustomAITool 形式定義
5
5
  */
6
6
 
7
7
  import type { CustomAITool } from "../types/tools.js";
@@ -23,11 +23,11 @@ export const CLAUDE_CODE_TOOL: CustomAITool = {
23
23
  };
24
24
 
25
25
  /**
26
- * Codex CLI のビルトイン定義
26
+ * Codex のビルトイン定義
27
27
  */
28
28
  export const CODEX_CLI_TOOL: CustomAITool = {
29
29
  id: "codex-cli",
30
- displayName: "Codex CLI",
30
+ displayName: "Codex",
31
31
  type: "bunx",
32
32
  command: "@openai/codex@latest",
33
33
  defaultArgs: ["--auto-approve", "--verbose"],
@@ -38,7 +38,45 @@ export const CODEX_CLI_TOOL: CustomAITool = {
38
38
  },
39
39
  };
40
40
 
41
+ /**
42
+ * Gemini のビルトイン定義
43
+ */
44
+ export const GEMINI_CLI_TOOL: CustomAITool = {
45
+ id: "gemini-cli",
46
+ displayName: "Gemini",
47
+ type: "bunx",
48
+ command: "@google/gemini-cli@latest",
49
+ modeArgs: {
50
+ normal: [],
51
+ continue: ["-r", "latest"],
52
+ resume: ["-r", "latest"],
53
+ },
54
+ permissionSkipArgs: ["-y"],
55
+ };
56
+
57
+ /**
58
+ * Qwen のビルトイン定義
59
+ */
60
+ export const QWEN_CLI_TOOL: CustomAITool = {
61
+ id: "qwen-cli",
62
+ displayName: "Qwen",
63
+ type: "bunx",
64
+ command: "@qwen-code/qwen-code@latest",
65
+ defaultArgs: ["--checkpointing"],
66
+ modeArgs: {
67
+ normal: [],
68
+ continue: [],
69
+ resume: [],
70
+ },
71
+ permissionSkipArgs: ["--yolo"],
72
+ };
73
+
41
74
  /**
42
75
  * すべてのビルトインツール
43
76
  */
44
- export const BUILTIN_TOOLS: CustomAITool[] = [CLAUDE_CODE_TOOL, CODEX_CLI_TOOL];
77
+ export const BUILTIN_TOOLS: CustomAITool[] = [
78
+ CLAUDE_CODE_TOOL,
79
+ CODEX_CLI_TOOL,
80
+ GEMINI_CLI_TOOL,
81
+ QWEN_CLI_TOOL,
82
+ ];
@@ -88,12 +88,7 @@ export function resetConfigCache(): void {
88
88
  * セッションデータの保存・読み込み
89
89
  */
90
90
  function getSessionFilePath(repositoryRoot: string): string {
91
- const sessionDir = path.join(
92
- homedir(),
93
- ".config",
94
- "gwt",
95
- "sessions",
96
- );
91
+ const sessionDir = path.join(homedir(), ".config", "gwt", "sessions");
97
92
  const repoName = path.basename(repositoryRoot);
98
93
  const repoHash = Buffer.from(repositoryRoot)
99
94
  .toString("base64")
@@ -152,12 +147,7 @@ export async function loadSession(
152
147
 
153
148
  export async function getAllSessions(): Promise<SessionData[]> {
154
149
  try {
155
- const sessionDir = path.join(
156
- homedir(),
157
- ".config",
158
- "gwt",
159
- "sessions",
160
- );
150
+ const sessionDir = path.join(homedir(), ".config", "gwt", "sessions");
161
151
  const { readdir } = await import("node:fs/promises");
162
152
 
163
153
  const files = await readdir(sessionDir);
@@ -7,7 +7,14 @@
7
7
 
8
8
  import { homedir } from "node:os";
9
9
  import path from "node:path";
10
- import { readFile, writeFile, mkdir, rename, access, cp } from "node:fs/promises";
10
+ import {
11
+ readFile,
12
+ writeFile,
13
+ mkdir,
14
+ rename,
15
+ access,
16
+ cp,
17
+ } from "node:fs/promises";
11
18
  import type {
12
19
  ToolsConfig,
13
20
  CustomAITool,
@@ -20,11 +27,12 @@ import { BUILTIN_TOOLS } from "./builtin-tools.js";
20
27
  * 環境変数の優先順位: GWT_HOME > CLAUDE_WORKTREE_HOME (後方互換性) > ホームディレクトリ
21
28
  */
22
29
  export const WORKTREE_HOME =
23
- (process.env.GWT_HOME && process.env.GWT_HOME.trim().length > 0)
30
+ process.env.GWT_HOME && process.env.GWT_HOME.trim().length > 0
24
31
  ? process.env.GWT_HOME
25
- : (process.env.CLAUDE_WORKTREE_HOME && process.env.CLAUDE_WORKTREE_HOME.trim().length > 0)
26
- ? process.env.CLAUDE_WORKTREE_HOME
27
- : homedir();
32
+ : process.env.CLAUDE_WORKTREE_HOME &&
33
+ process.env.CLAUDE_WORKTREE_HOME.trim().length > 0
34
+ ? process.env.CLAUDE_WORKTREE_HOME
35
+ : homedir();
28
36
 
29
37
  const LEGACY_CONFIG_DIR = path.join(homedir(), ".claude-worktree");
30
38
  export const CONFIG_DIR = path.join(WORKTREE_HOME, ".gwt");
@@ -55,7 +63,9 @@ async function migrateLegacyConfig(): Promise<void> {
55
63
  // レガシーディレクトリを新しいディレクトリにコピー
56
64
  await mkdir(path.dirname(CONFIG_DIR), { recursive: true });
57
65
  await cp(LEGACY_CONFIG_DIR, CONFIG_DIR, { recursive: true });
58
- console.log(`✅ Migrated configuration from ${LEGACY_CONFIG_DIR} to ${CONFIG_DIR}`);
66
+ console.log(
67
+ `✅ Migrated configuration from ${LEGACY_CONFIG_DIR} to ${CONFIG_DIR}`,
68
+ );
59
69
  } catch (error) {
60
70
  // 移行に失敗しても継続(エラーログのみ)
61
71
  if (process.env.DEBUG) {
package/src/gemini.ts ADDED
@@ -0,0 +1,207 @@
1
+ import { execa } from "execa";
2
+ import chalk from "chalk";
3
+ import { existsSync } from "fs";
4
+ import { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
5
+
6
+ const GEMINI_CLI_PACKAGE = "@google/gemini-cli@latest";
7
+
8
+ export class GeminiError extends Error {
9
+ constructor(
10
+ message: string,
11
+ public cause?: unknown,
12
+ ) {
13
+ super(message);
14
+ this.name = "GeminiError";
15
+ }
16
+ }
17
+
18
+ export async function launchGeminiCLI(
19
+ worktreePath: string,
20
+ options: {
21
+ skipPermissions?: boolean;
22
+ mode?: "normal" | "continue" | "resume";
23
+ extraArgs?: string[];
24
+ envOverrides?: Record<string, string>;
25
+ model?: string;
26
+ } = {},
27
+ ): Promise<void> {
28
+ const terminal = getTerminalStreams();
29
+
30
+ try {
31
+ // Check if the worktree path exists
32
+ if (!existsSync(worktreePath)) {
33
+ throw new Error(`Worktree path does not exist: ${worktreePath}`);
34
+ }
35
+
36
+ console.log(chalk.blue("🚀 Launching Gemini CLI..."));
37
+ console.log(chalk.gray(` Working directory: ${worktreePath}`));
38
+
39
+ const args: string[] = [];
40
+
41
+ if (options.model) {
42
+ args.push("--model", options.model);
43
+ console.log(chalk.green(` 🎯 Model: ${options.model}`));
44
+ }
45
+
46
+ // Handle execution mode
47
+ switch (options.mode) {
48
+ case "continue":
49
+ args.push("-r", "latest");
50
+ console.log(chalk.cyan(" ⏭️ Continuing most recent session"));
51
+ break;
52
+ case "resume":
53
+ args.push("-r", "latest");
54
+ console.log(chalk.cyan(" 🔄 Resuming session"));
55
+ break;
56
+ case "normal":
57
+ default:
58
+ console.log(chalk.green(" ✨ Starting new session"));
59
+ break;
60
+ }
61
+
62
+ // Handle skip permissions (YOLO mode)
63
+ if (options.skipPermissions) {
64
+ args.push("-y");
65
+ console.log(
66
+ chalk.yellow(" ⚠️ Auto-approving all actions (YOLO mode)"),
67
+ );
68
+ }
69
+
70
+ // Append any pass-through arguments after our flags
71
+ if (options.extraArgs && options.extraArgs.length > 0) {
72
+ args.push(...options.extraArgs);
73
+ }
74
+
75
+ terminal.exitRawMode();
76
+
77
+ const baseEnv = {
78
+ ...process.env,
79
+ ...(options.envOverrides ?? {}),
80
+ };
81
+
82
+ const childStdio = createChildStdio();
83
+
84
+ // Auto-detect locally installed gemini command
85
+ const hasLocalGemini = await isGeminiCommandAvailable();
86
+
87
+ try {
88
+ if (hasLocalGemini) {
89
+ // Use locally installed gemini command
90
+ console.log(
91
+ chalk.green(" ✨ Using locally installed gemini command"),
92
+ );
93
+ await execa("gemini", args, {
94
+ cwd: worktreePath,
95
+ shell: true,
96
+ stdin: childStdio.stdin,
97
+ stdout: childStdio.stdout,
98
+ stderr: childStdio.stderr,
99
+ env: baseEnv,
100
+ } as any);
101
+ } else {
102
+ // Fallback to bunx
103
+ console.log(
104
+ chalk.cyan(" 🔄 Falling back to bunx @google/gemini-cli@latest"),
105
+ );
106
+ console.log(
107
+ chalk.yellow(
108
+ " 💡 Recommended: Install Gemini CLI globally for faster startup",
109
+ ),
110
+ );
111
+ console.log(chalk.yellow(" npm install -g @google/gemini-cli"));
112
+ console.log("");
113
+ // Wait 2 seconds to let user read the message
114
+ await new Promise((resolve) => setTimeout(resolve, 2000));
115
+ await execa("bunx", [GEMINI_CLI_PACKAGE, ...args], {
116
+ cwd: worktreePath,
117
+ shell: true,
118
+ stdin: childStdio.stdin,
119
+ stdout: childStdio.stdout,
120
+ stderr: childStdio.stderr,
121
+ env: baseEnv,
122
+ } as any);
123
+ }
124
+ } finally {
125
+ childStdio.cleanup();
126
+ }
127
+ } catch (error: any) {
128
+ const hasLocalGemini = await isGeminiCommandAvailable();
129
+ let errorMessage: string;
130
+
131
+ if (error.code === "ENOENT") {
132
+ if (hasLocalGemini) {
133
+ errorMessage =
134
+ "gemini command not found. Please ensure Gemini CLI is properly installed.";
135
+ } else {
136
+ errorMessage =
137
+ "bunx command not found. Please ensure Bun is installed so Gemini CLI can run via bunx.";
138
+ }
139
+ } else {
140
+ errorMessage = `Failed to launch Gemini CLI: ${error.message || "Unknown error"}`;
141
+ }
142
+
143
+ if (process.platform === "win32") {
144
+ console.error(chalk.red("\n💡 Windows troubleshooting tips:"));
145
+ if (hasLocalGemini) {
146
+ console.error(
147
+ chalk.yellow(
148
+ " 1. Confirm that Gemini CLI is installed and the 'gemini' command is on PATH",
149
+ ),
150
+ );
151
+ console.error(
152
+ chalk.yellow(' 2. Run "gemini --version" to verify the setup'),
153
+ );
154
+ } else {
155
+ console.error(
156
+ chalk.yellow(
157
+ " 1. Confirm that Bun is installed and bunx is available",
158
+ ),
159
+ );
160
+ console.error(
161
+ chalk.yellow(
162
+ ' 2. Run "bunx @google/gemini-cli@latest -- --version" to verify the setup',
163
+ ),
164
+ );
165
+ }
166
+ console.error(
167
+ chalk.yellow(" 3. Restart your terminal or IDE to refresh PATH"),
168
+ );
169
+ }
170
+
171
+ throw new GeminiError(errorMessage, error);
172
+ } finally {
173
+ terminal.exitRawMode();
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Check if locally installed `gemini` command is available
179
+ * @returns true if `gemini` command exists in PATH, false otherwise
180
+ */
181
+ async function isGeminiCommandAvailable(): Promise<boolean> {
182
+ try {
183
+ const command = process.platform === "win32" ? "where" : "which";
184
+ await execa(command, ["gemini"], { shell: true });
185
+ return true;
186
+ } catch {
187
+ // gemini command not found in PATH
188
+ return false;
189
+ }
190
+ }
191
+
192
+ export async function isGeminiCLIAvailable(): Promise<boolean> {
193
+ try {
194
+ await execa("bunx", [GEMINI_CLI_PACKAGE, "--version"], { shell: true });
195
+ return true;
196
+ } catch (error: any) {
197
+ if (error.code === "ENOENT") {
198
+ console.error(chalk.yellow("\n⚠️ bunx command not found"));
199
+ console.error(
200
+ chalk.gray(
201
+ " Install Bun and confirm that bunx is available before continuing",
202
+ ),
203
+ );
204
+ }
205
+ return false;
206
+ }
207
+ }
package/src/git.ts CHANGED
@@ -351,7 +351,8 @@ function getBranchType(branchName: string): BranchInfo["branchType"] {
351
351
  if (branchName === "main" || branchName === "master") return "main";
352
352
  if (branchName === "develop" || branchName === "dev") return "develop";
353
353
  if (branchName.startsWith("feature/")) return "feature";
354
- if (branchName.startsWith("bugfix/") || branchName.startsWith("bug/")) return "bugfix";
354
+ if (branchName.startsWith("bugfix/") || branchName.startsWith("bug/"))
355
+ return "bugfix";
355
356
  if (branchName.startsWith("hotfix/")) return "hotfix";
356
357
  if (branchName.startsWith("release/")) return "release";
357
358
  return "other";
package/src/index.ts CHANGED
@@ -10,7 +10,13 @@ import {
10
10
  GitError,
11
11
  } from "./git.js";
12
12
  import { launchClaudeCode } from "./claude.js";
13
- import { launchCodexCLI, CodexError } from "./codex.js";
13
+ import {
14
+ launchCodexCLI,
15
+ CodexError,
16
+ type CodexReasoningEffort,
17
+ } from "./codex.js";
18
+ import { launchGeminiCLI, GeminiError } from "./gemini.js";
19
+ import { launchQwenCLI, QwenError } from "./qwen.js";
14
20
  import {
15
21
  WorktreeOrchestrator,
16
22
  type EnsureWorktreeOptions,
@@ -96,6 +102,8 @@ function isRecoverableError(error: unknown): boolean {
96
102
  error instanceof GitError ||
97
103
  error instanceof WorktreeError ||
98
104
  error instanceof CodexError ||
105
+ error instanceof GeminiError ||
106
+ error instanceof QwenError ||
99
107
  error instanceof DependencyInstallError
100
108
  ) {
101
109
  return true;
@@ -106,6 +114,8 @@ function isRecoverableError(error: unknown): boolean {
106
114
  error.name === "GitError" ||
107
115
  error.name === "WorktreeError" ||
108
116
  error.name === "CodexError" ||
117
+ error.name === "GeminiError" ||
118
+ error.name === "QwenError" ||
109
119
  error.name === "DependencyInstallError"
110
120
  );
111
121
  }
@@ -119,6 +129,8 @@ function isRecoverableError(error: unknown): boolean {
119
129
  name === "GitError" ||
120
130
  name === "WorktreeError" ||
121
131
  name === "CodexError" ||
132
+ name === "GeminiError" ||
133
+ name === "QwenError" ||
122
134
  name === "DependencyInstallError"
123
135
  );
124
136
  }
@@ -284,11 +296,17 @@ export async function handleAIToolWorkflow(
284
296
  tool,
285
297
  mode,
286
298
  skipPermissions,
299
+ model,
300
+ inferenceLevel,
287
301
  } = selectionResult;
288
302
 
289
303
  const branchLabel = displayName ?? branch;
304
+ const modelInfo =
305
+ model || inferenceLevel
306
+ ? `, model=${model ?? "default"}${inferenceLevel ? `/${inferenceLevel}` : ""}`
307
+ : "";
290
308
  printInfo(
291
- `Selected: ${branchLabel} with ${tool} (${mode} mode, skipPermissions: ${skipPermissions})`,
309
+ `Selected: ${branchLabel} with ${tool} (${mode} mode${modelInfo}, skipPermissions: ${skipPermissions})`,
292
310
  );
293
311
 
294
312
  try {
@@ -520,7 +538,12 @@ export async function handleAIToolWorkflow(
520
538
  // Builtin tools use their dedicated launch functions
521
539
  // Custom tools use the generic launchCustomAITool function
522
540
  if (tool === "claude-code") {
523
- await launchClaudeCode(worktreePath, {
541
+ const launchOptions: {
542
+ mode?: "normal" | "continue" | "resume";
543
+ skipPermissions?: boolean;
544
+ envOverrides?: Record<string, string>;
545
+ model?: string;
546
+ } = {
524
547
  mode:
525
548
  mode === "resume"
526
549
  ? "resume"
@@ -529,9 +552,19 @@ export async function handleAIToolWorkflow(
529
552
  : "normal",
530
553
  skipPermissions,
531
554
  envOverrides: sharedEnv,
532
- });
555
+ };
556
+ if (model) {
557
+ launchOptions.model = model;
558
+ }
559
+ await launchClaudeCode(worktreePath, launchOptions);
533
560
  } else if (tool === "codex-cli") {
534
- await launchCodexCLI(worktreePath, {
561
+ const launchOptions: {
562
+ mode?: "normal" | "continue" | "resume";
563
+ bypassApprovals?: boolean;
564
+ envOverrides?: Record<string, string>;
565
+ model?: string;
566
+ reasoningEffort?: CodexReasoningEffort;
567
+ } = {
535
568
  mode:
536
569
  mode === "resume"
537
570
  ? "resume"
@@ -540,7 +573,54 @@ export async function handleAIToolWorkflow(
540
573
  : "normal",
541
574
  bypassApprovals: skipPermissions,
542
575
  envOverrides: sharedEnv,
543
- });
576
+ };
577
+ if (model) {
578
+ launchOptions.model = model;
579
+ }
580
+ if (inferenceLevel) {
581
+ launchOptions.reasoningEffort = inferenceLevel as CodexReasoningEffort;
582
+ }
583
+ await launchCodexCLI(worktreePath, launchOptions);
584
+ } else if (tool === "gemini-cli") {
585
+ const launchOptions: {
586
+ mode?: "normal" | "continue" | "resume";
587
+ skipPermissions?: boolean;
588
+ envOverrides?: Record<string, string>;
589
+ model?: string;
590
+ } = {
591
+ mode:
592
+ mode === "resume"
593
+ ? "resume"
594
+ : mode === "continue"
595
+ ? "continue"
596
+ : "normal",
597
+ skipPermissions,
598
+ envOverrides: sharedEnv,
599
+ };
600
+ if (model) {
601
+ launchOptions.model = model;
602
+ }
603
+ await launchGeminiCLI(worktreePath, launchOptions);
604
+ } else if (tool === "qwen-cli") {
605
+ const launchOptions: {
606
+ mode?: "normal" | "continue" | "resume";
607
+ skipPermissions?: boolean;
608
+ envOverrides?: Record<string, string>;
609
+ model?: string;
610
+ } = {
611
+ mode:
612
+ mode === "resume"
613
+ ? "resume"
614
+ : mode === "continue"
615
+ ? "continue"
616
+ : "normal",
617
+ skipPermissions,
618
+ envOverrides: sharedEnv,
619
+ };
620
+ if (model) {
621
+ launchOptions.model = model;
622
+ }
623
+ await launchQwenCLI(worktreePath, launchOptions);
544
624
  } else {
545
625
  // Custom tool
546
626
  printInfo(`Launching custom tool: ${toolConfig.displayName}`);