@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.
- package/README.ja.md +4 -4
- package/README.md +4 -4
- package/dist/cli/ui/components/App.d.ts +4 -4
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +144 -105
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
- package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Confirm.js +7 -7
- package/dist/cli/ui/components/common/Confirm.js.map +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
- package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
- package/dist/cli/ui/components/common/Input.d.ts +7 -2
- package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Input.js +12 -4
- package/dist/cli/ui/components/common/Input.js.map +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
- package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +1 -1
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +11 -12
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts +8 -4
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +122 -48
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
- package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
- package/dist/client/index.html +1 -1
- package/dist/config/builtin-tools.d.ts +10 -2
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +40 -4
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +4 -3
- package/dist/config/tools.js.map +1 -1
- package/dist/gemini.d.ts +12 -0
- package/dist/gemini.d.ts.map +1 -0
- package/dist/gemini.js +154 -0
- package/dist/gemini.js.map +1 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -1
- package/dist/qwen.d.ts +12 -0
- package/dist/qwen.d.ts.map +1 -0
- package/dist/qwen.js +154 -0
- package/dist/qwen.js.map +1 -0
- package/dist/services/git.service.d.ts.map +1 -1
- package/dist/services/git.service.js.map +1 -1
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +1 -1
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
- package/dist/web/client/src/components/EnvEditor.js +7 -4
- package/dist/web/client/src/components/EnvEditor.js.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.js +10 -4
- package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
- package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
- package/package.json +2 -1
- package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
- package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
- package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
- package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
- package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
- package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +40 -34
- package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
- package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
- package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
- package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
- package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
- package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
- package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +496 -75
- package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
- package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
- package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
- package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
- package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
- package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
- package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
- package/src/cli/ui/components/App.tsx +247 -150
- package/src/cli/ui/components/common/Confirm.tsx +13 -9
- package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
- package/src/cli/ui/components/common/Input.tsx +26 -4
- package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
- package/src/cli/ui/components/common/Select.tsx +28 -17
- package/src/cli/ui/components/parts/Header.test.tsx +5 -15
- package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +19 -13
- package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
- package/src/cli/ui/components/screens/BranchListScreen.tsx +187 -62
- package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
- package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
- package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
- package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
- package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
- package/src/cli/ui/types.ts +8 -1
- package/src/config/builtin-tools.ts +42 -4
- package/src/config/index.ts +2 -12
- package/src/config/tools.ts +16 -6
- package/src/gemini.ts +202 -0
- package/src/git.ts +2 -1
- package/src/index.ts +30 -0
- package/src/qwen.ts +208 -0
- package/src/services/git.service.ts +2 -1
- package/src/web/client/src/components/BranchGraph.tsx +3 -2
- package/src/web/client/src/components/EnvEditor.tsx +44 -11
- package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
- package/src/web/client/src/pages/BranchListPage.tsx +37 -13
- package/src/web/client/src/pages/ConfigManagementPage.tsx +28 -9
package/src/cli/ui/types.ts
CHANGED
|
@@ -8,7 +8,14 @@ export interface WorktreeInfo {
|
|
|
8
8
|
export interface BranchInfo {
|
|
9
9
|
name: string;
|
|
10
10
|
type: "local" | "remote";
|
|
11
|
-
branchType:
|
|
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 Code
|
|
4
|
+
* Claude Code、Codex、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
|
|
26
|
+
* Codex のビルトイン定義
|
|
27
27
|
*/
|
|
28
28
|
export const CODEX_CLI_TOOL: CustomAITool = {
|
|
29
29
|
id: "codex-cli",
|
|
30
|
-
displayName: "Codex
|
|
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[] = [
|
|
77
|
+
export const BUILTIN_TOOLS: CustomAITool[] = [
|
|
78
|
+
CLAUDE_CODE_TOOL,
|
|
79
|
+
CODEX_CLI_TOOL,
|
|
80
|
+
GEMINI_CLI_TOOL,
|
|
81
|
+
QWEN_CLI_TOOL,
|
|
82
|
+
];
|
package/src/config/index.ts
CHANGED
|
@@ -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);
|
package/src/config/tools.ts
CHANGED
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
10
|
-
import {
|
|
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
|
-
|
|
30
|
+
process.env.GWT_HOME && process.env.GWT_HOME.trim().length > 0
|
|
24
31
|
? process.env.GWT_HOME
|
|
25
|
-
:
|
|
26
|
-
|
|
27
|
-
|
|
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(
|
|
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/"))
|
|
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/"))
|
|
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
|
|
106
|
+
baseRef、Git
|
|
107
|
+
upstream、merge-baseヒューリスティクスを用いて推定したベースブランチ単位で
|
|
107
108
|
派生ノードをレーン表示します。
|
|
108
109
|
</p>
|
|
109
110
|
</div>
|