@akiojin/gwt 2.1.1 → 2.3.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 (149) hide show
  1. package/README.ja.md +4 -4
  2. package/README.md +4 -4
  3. package/dist/cli/ui/components/App.d.ts +4 -4
  4. package/dist/cli/ui/components/App.d.ts.map +1 -1
  5. package/dist/cli/ui/components/App.js +144 -105
  6. package/dist/cli/ui/components/App.js.map +1 -1
  7. package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
  8. package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
  9. package/dist/cli/ui/components/common/Confirm.js +7 -7
  10. package/dist/cli/ui/components/common/Confirm.js.map +1 -1
  11. package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
  12. package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
  13. package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
  14. package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
  15. package/dist/cli/ui/components/common/Input.d.ts +7 -2
  16. package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
  17. package/dist/cli/ui/components/common/Input.js +12 -4
  18. package/dist/cli/ui/components/common/Input.js.map +1 -1
  19. package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
  20. package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
  21. package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
  22. package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
  23. package/dist/cli/ui/components/common/Select.d.ts +1 -1
  24. package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
  25. package/dist/cli/ui/components/common/Select.js +11 -12
  26. package/dist/cli/ui/components/common/Select.js.map +1 -1
  27. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +2 -2
  28. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
  29. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
  30. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
  31. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
  32. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
  33. package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
  34. package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
  35. package/dist/cli/ui/components/screens/BranchListScreen.d.ts +8 -4
  36. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  37. package/dist/cli/ui/components/screens/BranchListScreen.js +122 -48
  38. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  39. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
  40. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
  41. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
  42. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
  43. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
  44. package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
  45. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
  46. package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
  47. package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
  48. package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
  49. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
  50. package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
  51. package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
  52. package/dist/cli/ui/types.d.ts.map +1 -1
  53. package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
  54. package/dist/client/index.html +1 -1
  55. package/dist/config/builtin-tools.d.ts +10 -2
  56. package/dist/config/builtin-tools.d.ts.map +1 -1
  57. package/dist/config/builtin-tools.js +40 -4
  58. package/dist/config/builtin-tools.js.map +1 -1
  59. package/dist/config/index.d.ts.map +1 -1
  60. package/dist/config/index.js.map +1 -1
  61. package/dist/config/tools.d.ts.map +1 -1
  62. package/dist/config/tools.js +4 -3
  63. package/dist/config/tools.js.map +1 -1
  64. package/dist/gemini.d.ts +12 -0
  65. package/dist/gemini.d.ts.map +1 -0
  66. package/dist/gemini.js +154 -0
  67. package/dist/gemini.js.map +1 -0
  68. package/dist/git.d.ts.map +1 -1
  69. package/dist/git.js.map +1 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +30 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/qwen.d.ts +12 -0
  74. package/dist/qwen.d.ts.map +1 -0
  75. package/dist/qwen.js +154 -0
  76. package/dist/qwen.js.map +1 -0
  77. package/dist/services/git.service.d.ts.map +1 -1
  78. package/dist/services/git.service.js.map +1 -1
  79. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  80. package/dist/web/client/src/components/BranchGraph.js +1 -1
  81. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  82. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  83. package/dist/web/client/src/components/EnvEditor.js +7 -4
  84. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  85. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  86. package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
  87. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  88. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  89. package/dist/web/client/src/pages/BranchListPage.js +10 -4
  90. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  91. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  92. package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
  93. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  94. package/package.json +2 -1
  95. package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
  96. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
  97. package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
  98. package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
  99. package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
  100. package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
  101. package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
  102. package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +40 -34
  103. package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
  104. package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
  105. package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
  106. package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
  107. package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
  108. package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
  109. package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
  110. package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
  111. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +496 -75
  112. package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
  113. package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
  114. package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
  115. package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
  116. package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
  117. package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
  118. package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
  119. package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
  120. package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
  121. package/src/cli/ui/components/App.tsx +247 -150
  122. package/src/cli/ui/components/common/Confirm.tsx +13 -9
  123. package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
  124. package/src/cli/ui/components/common/Input.tsx +26 -4
  125. package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
  126. package/src/cli/ui/components/common/Select.tsx +28 -17
  127. package/src/cli/ui/components/parts/Header.test.tsx +5 -15
  128. package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +19 -13
  129. package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
  130. package/src/cli/ui/components/screens/BranchListScreen.tsx +187 -62
  131. package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
  132. package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
  133. package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
  134. package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
  135. package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
  136. package/src/cli/ui/types.ts +8 -1
  137. package/src/config/builtin-tools.ts +42 -4
  138. package/src/config/index.ts +2 -12
  139. package/src/config/tools.ts +16 -6
  140. package/src/gemini.ts +202 -0
  141. package/src/git.ts +2 -1
  142. package/src/index.ts +30 -0
  143. package/src/qwen.ts +208 -0
  144. package/src/services/git.service.ts +2 -1
  145. package/src/web/client/src/components/BranchGraph.tsx +3 -2
  146. package/src/web/client/src/components/EnvEditor.tsx +44 -11
  147. package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
  148. package/src/web/client/src/pages/BranchListPage.tsx +37 -13
  149. package/src/web/client/src/pages/ConfigManagementPage.tsx +28 -9
@@ -8,7 +8,14 @@ export interface WorktreeInfo {
8
8
  export interface BranchInfo {
9
9
  name: string;
10
10
  type: "local" | "remote";
11
- branchType: "feature" | "bugfix" | "hotfix" | "release" | "main" | "develop" | "other";
11
+ branchType:
12
+ | "feature"
13
+ | "bugfix"
14
+ | "hotfix"
15
+ | "release"
16
+ | "main"
17
+ | "develop"
18
+ | "other";
12
19
  isCurrent: boolean;
13
20
  description?: string;
14
21
  worktree?: WorktreeInfo;
@@ -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,202 @@
1
+ import { execa } from "execa";
2
+ import chalk from "chalk";
3
+ import { platform } from "os";
4
+ import { existsSync } from "fs";
5
+ import { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
6
+
7
+ const GEMINI_CLI_PACKAGE = "@google/gemini-cli@latest";
8
+
9
+ export class GeminiError extends Error {
10
+ constructor(
11
+ message: string,
12
+ public cause?: unknown,
13
+ ) {
14
+ super(message);
15
+ this.name = "GeminiError";
16
+ }
17
+ }
18
+
19
+ export async function launchGeminiCLI(
20
+ worktreePath: string,
21
+ options: {
22
+ skipPermissions?: boolean;
23
+ mode?: "normal" | "continue" | "resume";
24
+ extraArgs?: string[];
25
+ envOverrides?: Record<string, 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
+ // Handle execution mode
42
+ switch (options.mode) {
43
+ case "continue":
44
+ args.push("-r", "latest");
45
+ console.log(chalk.cyan(" ⏭️ Continuing most recent session"));
46
+ break;
47
+ case "resume":
48
+ args.push("-r", "latest");
49
+ console.log(chalk.cyan(" 🔄 Resuming session"));
50
+ break;
51
+ case "normal":
52
+ default:
53
+ console.log(chalk.green(" ✨ Starting new session"));
54
+ break;
55
+ }
56
+
57
+ // Handle skip permissions (YOLO mode)
58
+ if (options.skipPermissions) {
59
+ args.push("-y");
60
+ console.log(
61
+ chalk.yellow(" ⚠️ Auto-approving all actions (YOLO mode)"),
62
+ );
63
+ }
64
+
65
+ // Append any pass-through arguments after our flags
66
+ if (options.extraArgs && options.extraArgs.length > 0) {
67
+ args.push(...options.extraArgs);
68
+ }
69
+
70
+ terminal.exitRawMode();
71
+
72
+ const baseEnv = {
73
+ ...process.env,
74
+ ...(options.envOverrides ?? {}),
75
+ };
76
+
77
+ const childStdio = createChildStdio();
78
+
79
+ // Auto-detect locally installed gemini command
80
+ const hasLocalGemini = await isGeminiCommandAvailable();
81
+
82
+ try {
83
+ if (hasLocalGemini) {
84
+ // Use locally installed gemini command
85
+ console.log(
86
+ chalk.green(" ✨ Using locally installed gemini command"),
87
+ );
88
+ await execa("gemini", args, {
89
+ cwd: worktreePath,
90
+ shell: true,
91
+ stdin: childStdio.stdin,
92
+ stdout: childStdio.stdout,
93
+ stderr: childStdio.stderr,
94
+ env: baseEnv,
95
+ } as any);
96
+ } else {
97
+ // Fallback to bunx
98
+ console.log(
99
+ chalk.cyan(" 🔄 Falling back to bunx @google/gemini-cli@latest"),
100
+ );
101
+ console.log(
102
+ chalk.yellow(
103
+ " 💡 Recommended: Install Gemini CLI globally for faster startup",
104
+ ),
105
+ );
106
+ console.log(chalk.yellow(" npm install -g @google/gemini-cli"));
107
+ console.log("");
108
+ // Wait 2 seconds to let user read the message
109
+ await new Promise((resolve) => setTimeout(resolve, 2000));
110
+ await execa("bunx", [GEMINI_CLI_PACKAGE, ...args], {
111
+ cwd: worktreePath,
112
+ shell: true,
113
+ stdin: childStdio.stdin,
114
+ stdout: childStdio.stdout,
115
+ stderr: childStdio.stderr,
116
+ env: baseEnv,
117
+ } as any);
118
+ }
119
+ } finally {
120
+ childStdio.cleanup();
121
+ }
122
+ } catch (error: any) {
123
+ const hasLocalGemini = await isGeminiCommandAvailable();
124
+ let errorMessage: string;
125
+
126
+ if (error.code === "ENOENT") {
127
+ if (hasLocalGemini) {
128
+ errorMessage =
129
+ "gemini command not found. Please ensure Gemini CLI is properly installed.";
130
+ } else {
131
+ errorMessage =
132
+ "bunx command not found. Please ensure Bun is installed so Gemini CLI can run via bunx.";
133
+ }
134
+ } else {
135
+ errorMessage = `Failed to launch Gemini CLI: ${error.message || "Unknown error"}`;
136
+ }
137
+
138
+ if (platform() === "win32") {
139
+ console.error(chalk.red("\n💡 Windows troubleshooting tips:"));
140
+ if (hasLocalGemini) {
141
+ console.error(
142
+ chalk.yellow(
143
+ " 1. Confirm that Gemini CLI is installed and the 'gemini' command is on PATH",
144
+ ),
145
+ );
146
+ console.error(
147
+ chalk.yellow(' 2. Run "gemini --version" to verify the setup'),
148
+ );
149
+ } else {
150
+ console.error(
151
+ chalk.yellow(
152
+ " 1. Confirm that Bun is installed and bunx is available",
153
+ ),
154
+ );
155
+ console.error(
156
+ chalk.yellow(
157
+ ' 2. Run "bunx @google/gemini-cli@latest -- --version" to verify the setup',
158
+ ),
159
+ );
160
+ }
161
+ console.error(
162
+ chalk.yellow(" 3. Restart your terminal or IDE to refresh PATH"),
163
+ );
164
+ }
165
+
166
+ throw new GeminiError(errorMessage, error);
167
+ } finally {
168
+ terminal.exitRawMode();
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Check if locally installed `gemini` command is available
174
+ * @returns true if `gemini` command exists in PATH, false otherwise
175
+ */
176
+ async function isGeminiCommandAvailable(): Promise<boolean> {
177
+ try {
178
+ const command = platform() === "win32" ? "where" : "which";
179
+ await execa(command, ["gemini"], { shell: true });
180
+ return true;
181
+ } catch {
182
+ // gemini command not found in PATH
183
+ return false;
184
+ }
185
+ }
186
+
187
+ export async function isGeminiCLIAvailable(): Promise<boolean> {
188
+ try {
189
+ await execa("bunx", [GEMINI_CLI_PACKAGE, "--version"], { shell: true });
190
+ return true;
191
+ } catch (error: any) {
192
+ if (error.code === "ENOENT") {
193
+ console.error(chalk.yellow("\n⚠️ bunx command not found"));
194
+ console.error(
195
+ chalk.gray(
196
+ " Install Bun and confirm that bunx is available before continuing",
197
+ ),
198
+ );
199
+ }
200
+ return false;
201
+ }
202
+ }
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
@@ -11,6 +11,8 @@ import {
11
11
  } from "./git.js";
12
12
  import { launchClaudeCode } from "./claude.js";
13
13
  import { launchCodexCLI, CodexError } from "./codex.js";
14
+ import { launchGeminiCLI, GeminiError } from "./gemini.js";
15
+ import { launchQwenCLI, QwenError } from "./qwen.js";
14
16
  import {
15
17
  WorktreeOrchestrator,
16
18
  type EnsureWorktreeOptions,
@@ -96,6 +98,8 @@ function isRecoverableError(error: unknown): boolean {
96
98
  error instanceof GitError ||
97
99
  error instanceof WorktreeError ||
98
100
  error instanceof CodexError ||
101
+ error instanceof GeminiError ||
102
+ error instanceof QwenError ||
99
103
  error instanceof DependencyInstallError
100
104
  ) {
101
105
  return true;
@@ -106,6 +110,8 @@ function isRecoverableError(error: unknown): boolean {
106
110
  error.name === "GitError" ||
107
111
  error.name === "WorktreeError" ||
108
112
  error.name === "CodexError" ||
113
+ error.name === "GeminiError" ||
114
+ error.name === "QwenError" ||
109
115
  error.name === "DependencyInstallError"
110
116
  );
111
117
  }
@@ -119,6 +125,8 @@ function isRecoverableError(error: unknown): boolean {
119
125
  name === "GitError" ||
120
126
  name === "WorktreeError" ||
121
127
  name === "CodexError" ||
128
+ name === "GeminiError" ||
129
+ name === "QwenError" ||
122
130
  name === "DependencyInstallError"
123
131
  );
124
132
  }
@@ -541,6 +549,28 @@ export async function handleAIToolWorkflow(
541
549
  bypassApprovals: skipPermissions,
542
550
  envOverrides: sharedEnv,
543
551
  });
552
+ } else if (tool === "gemini-cli") {
553
+ await launchGeminiCLI(worktreePath, {
554
+ mode:
555
+ mode === "resume"
556
+ ? "resume"
557
+ : mode === "continue"
558
+ ? "continue"
559
+ : "normal",
560
+ skipPermissions,
561
+ envOverrides: sharedEnv,
562
+ });
563
+ } else if (tool === "qwen-cli") {
564
+ await launchQwenCLI(worktreePath, {
565
+ mode:
566
+ mode === "resume"
567
+ ? "resume"
568
+ : mode === "continue"
569
+ ? "continue"
570
+ : "normal",
571
+ skipPermissions,
572
+ envOverrides: sharedEnv,
573
+ });
544
574
  } else {
545
575
  // Custom tool
546
576
  printInfo(`Launching custom tool: ${toolConfig.displayName}`);
package/src/qwen.ts ADDED
@@ -0,0 +1,208 @@
1
+ import { execa } from "execa";
2
+ import chalk from "chalk";
3
+ import { platform } from "os";
4
+ import { existsSync } from "fs";
5
+ import { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
6
+
7
+ const QWEN_CLI_PACKAGE = "@qwen-code/qwen-code@latest";
8
+
9
+ export class QwenError extends Error {
10
+ constructor(
11
+ message: string,
12
+ public cause?: unknown,
13
+ ) {
14
+ super(message);
15
+ this.name = "QwenError";
16
+ }
17
+ }
18
+
19
+ export async function launchQwenCLI(
20
+ worktreePath: string,
21
+ options: {
22
+ skipPermissions?: boolean;
23
+ mode?: "normal" | "continue" | "resume";
24
+ extraArgs?: string[];
25
+ envOverrides?: Record<string, 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 Qwen CLI..."));
37
+ console.log(chalk.gray(` Working directory: ${worktreePath}`));
38
+
39
+ const args: string[] = ["--checkpointing"];
40
+
41
+ // Handle execution mode
42
+ // Note: Qwen CLI doesn't have explicit continue/resume CLI options at startup.
43
+ // Session management is done via /chat commands during interactive sessions.
44
+ switch (options.mode) {
45
+ case "continue":
46
+ console.log(
47
+ chalk.cyan(
48
+ " ⏭️ Starting session (use /chat resume in the CLI to continue)",
49
+ ),
50
+ );
51
+ break;
52
+ case "resume":
53
+ console.log(
54
+ chalk.cyan(
55
+ " 🔄 Starting session (use /chat resume in the CLI to continue)",
56
+ ),
57
+ );
58
+ break;
59
+ case "normal":
60
+ default:
61
+ console.log(chalk.green(" ✨ Starting new session"));
62
+ break;
63
+ }
64
+
65
+ // Handle skip permissions (YOLO mode)
66
+ if (options.skipPermissions) {
67
+ args.push("--yolo");
68
+ console.log(
69
+ chalk.yellow(" ⚠️ Auto-approving all actions (YOLO mode)"),
70
+ );
71
+ }
72
+
73
+ // Append any pass-through arguments after our flags
74
+ if (options.extraArgs && options.extraArgs.length > 0) {
75
+ args.push(...options.extraArgs);
76
+ }
77
+
78
+ terminal.exitRawMode();
79
+
80
+ const baseEnv = {
81
+ ...process.env,
82
+ ...(options.envOverrides ?? {}),
83
+ };
84
+
85
+ const childStdio = createChildStdio();
86
+
87
+ // Auto-detect locally installed qwen command
88
+ const hasLocalQwen = await isQwenCommandAvailable();
89
+
90
+ try {
91
+ if (hasLocalQwen) {
92
+ // Use locally installed qwen command
93
+ console.log(chalk.green(" ✨ Using locally installed qwen command"));
94
+ await execa("qwen", args, {
95
+ cwd: worktreePath,
96
+ shell: true,
97
+ stdin: childStdio.stdin,
98
+ stdout: childStdio.stdout,
99
+ stderr: childStdio.stderr,
100
+ env: baseEnv,
101
+ } as any);
102
+ } else {
103
+ // Fallback to bunx
104
+ console.log(
105
+ chalk.cyan(" 🔄 Falling back to bunx @qwen-code/qwen-code@latest"),
106
+ );
107
+ console.log(
108
+ chalk.yellow(
109
+ " 💡 Recommended: Install Qwen CLI globally for faster startup",
110
+ ),
111
+ );
112
+ console.log(chalk.yellow(" npm install -g @qwen-code/qwen-code"));
113
+ console.log("");
114
+ // Wait 2 seconds to let user read the message
115
+ await new Promise((resolve) => setTimeout(resolve, 2000));
116
+ await execa("bunx", [QWEN_CLI_PACKAGE, ...args], {
117
+ cwd: worktreePath,
118
+ shell: true,
119
+ stdin: childStdio.stdin,
120
+ stdout: childStdio.stdout,
121
+ stderr: childStdio.stderr,
122
+ env: baseEnv,
123
+ } as any);
124
+ }
125
+ } finally {
126
+ childStdio.cleanup();
127
+ }
128
+ } catch (error: any) {
129
+ const hasLocalQwen = await isQwenCommandAvailable();
130
+ let errorMessage: string;
131
+
132
+ if (error.code === "ENOENT") {
133
+ if (hasLocalQwen) {
134
+ errorMessage =
135
+ "qwen command not found. Please ensure Qwen CLI is properly installed.";
136
+ } else {
137
+ errorMessage =
138
+ "bunx command not found. Please ensure Bun is installed so Qwen CLI can run via bunx.";
139
+ }
140
+ } else {
141
+ errorMessage = `Failed to launch Qwen CLI: ${error.message || "Unknown error"}`;
142
+ }
143
+
144
+ if (platform() === "win32") {
145
+ console.error(chalk.red("\n💡 Windows troubleshooting tips:"));
146
+ if (hasLocalQwen) {
147
+ console.error(
148
+ chalk.yellow(
149
+ " 1. Confirm that Qwen CLI is installed and the 'qwen' command is on PATH",
150
+ ),
151
+ );
152
+ console.error(
153
+ chalk.yellow(' 2. Run "qwen --version" to verify the setup'),
154
+ );
155
+ } else {
156
+ console.error(
157
+ chalk.yellow(
158
+ " 1. Confirm that Bun is installed and bunx is available",
159
+ ),
160
+ );
161
+ console.error(
162
+ chalk.yellow(
163
+ ' 2. Run "bunx @qwen-code/qwen-code@latest -- --version" to verify the setup',
164
+ ),
165
+ );
166
+ }
167
+ console.error(
168
+ chalk.yellow(" 3. Restart your terminal or IDE to refresh PATH"),
169
+ );
170
+ }
171
+
172
+ throw new QwenError(errorMessage, error);
173
+ } finally {
174
+ terminal.exitRawMode();
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Check if locally installed `qwen` command is available
180
+ * @returns true if `qwen` command exists in PATH, false otherwise
181
+ */
182
+ async function isQwenCommandAvailable(): Promise<boolean> {
183
+ try {
184
+ const command = platform() === "win32" ? "where" : "which";
185
+ await execa(command, ["qwen"], { shell: true });
186
+ return true;
187
+ } catch {
188
+ // qwen command not found in PATH
189
+ return false;
190
+ }
191
+ }
192
+
193
+ export async function isQwenCLIAvailable(): Promise<boolean> {
194
+ try {
195
+ await execa("bunx", [QWEN_CLI_PACKAGE, "--version"], { shell: true });
196
+ return true;
197
+ } catch (error: any) {
198
+ if (error.code === "ENOENT") {
199
+ console.error(chalk.yellow("\n⚠️ bunx command not found"));
200
+ console.error(
201
+ chalk.gray(
202
+ " Install Bun and confirm that bunx is available before continuing",
203
+ ),
204
+ );
205
+ }
206
+ return false;
207
+ }
208
+ }
@@ -52,7 +52,8 @@ export class GitService {
52
52
 
53
53
  private getBranchType(branchName: string): BranchInfo["branchType"] {
54
54
  if (branchName.startsWith("feature/")) return "feature";
55
- if (branchName.startsWith("bugfix/") || branchName.startsWith("bug/")) return "bugfix";
55
+ if (branchName.startsWith("bugfix/") || branchName.startsWith("bug/"))
56
+ return "bugfix";
56
57
  if (branchName.startsWith("hotfix/")) return "hotfix";
57
58
  if (branchName.startsWith("release/")) return "release";
58
59
  if (branchName === "main" || branchName === "master") return "main";
@@ -61,7 +61,7 @@ export function BranchGraph({ branches }: BranchGraphProps) {
61
61
 
62
62
  if (!laneMap.has(base)) {
63
63
  const baseNode =
64
- base !== UNKNOWN_BASE ? branchMap.get(base) ?? null : null;
64
+ base !== UNKNOWN_BASE ? (branchMap.get(base) ?? null) : null;
65
65
  laneMap.set(base, {
66
66
  id: base,
67
67
  baseLabel: base === UNKNOWN_BASE ? "ベース不明" : base,
@@ -103,7 +103,8 @@ export function BranchGraph({ branches }: BranchGraphProps) {
103
103
  <p className="branch-graph-panel__eyebrow">BRANCH GRAPH</p>
104
104
  <h2>ベースブランチの関係をグラフィカルに把握</h2>
105
105
  <p>
106
- baseRef、Git upstream、merge-baseヒューリスティクスを用いて推定したベースブランチ単位で
106
+ baseRef、Git
107
+ upstream、merge-baseヒューリスティクスを用いて推定したベースブランチ単位で
107
108
  派生ノードをレーン表示します。
108
109
  </p>
109
110
  </div>