@akiojin/gwt 2.6.1 → 2.8.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 +1 -1
- package/README.md +2 -2
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +5 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/hooks/useGitData.d.ts.map +1 -1
- package/dist/cli/ui/hooks/useGitData.js +27 -7
- package/dist/cli/ui/hooks/useGitData.js.map +1 -1
- package/dist/cli/ui/types.d.ts +4 -0
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +47 -0
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/client/assets/{index-DxHGLTNq.js → index-CNWntAlF.js} +15 -15
- package/dist/client/index.html +1 -1
- package/dist/config/index.d.ts +17 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +66 -1
- package/dist/config/index.js.map +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +114 -30
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/types/api.d.ts +11 -0
- package/dist/types/api.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +55 -0
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +35 -0
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/web/server/services/branches.d.ts.map +1 -1
- package/dist/web/server/services/branches.js +13 -2
- package/dist/web/server/services/branches.js.map +1 -1
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +3 -1
- package/dist/worktree.js.map +1 -1
- package/package.json +1 -8
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +53 -0
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +5 -0
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +37 -0
- package/src/cli/ui/components/screens/BranchListScreen.tsx +6 -1
- package/src/cli/ui/hooks/useGitData.ts +27 -7
- package/src/cli/ui/types.ts +5 -0
- package/src/cli/ui/utils/branchFormatter.ts +47 -0
- package/src/config/index.ts +87 -1
- package/src/git.ts +114 -30
- package/src/index.ts +3 -0
- package/src/types/api.ts +12 -0
- package/src/web/client/src/pages/BranchDetailPage.tsx +69 -1
- package/src/web/server/routes/sessions.ts +39 -0
- package/src/web/server/services/branches.ts +18 -2
- package/src/worktree.ts +7 -1
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
import { useConfig } from "../hooks/useConfig";
|
|
11
11
|
import { ApiError } from "../lib/api";
|
|
12
12
|
import { Terminal } from "../components/Terminal";
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
Branch,
|
|
15
|
+
CustomAITool,
|
|
16
|
+
LastToolUsage,
|
|
17
|
+
} from "../../../../types/api.js";
|
|
14
18
|
|
|
15
19
|
type ToolType = "claude-code" | "codex-cli" | "custom";
|
|
16
20
|
type ToolMode = "normal" | "continue" | "resume";
|
|
@@ -410,6 +414,29 @@ export function BranchDetailPage() {
|
|
|
410
414
|
.sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? ""));
|
|
411
415
|
}, [sessionsData, branch?.worktreePath]);
|
|
412
416
|
|
|
417
|
+
const latestToolUsage: LastToolUsage | null = useMemo(() => {
|
|
418
|
+
if (branch?.lastToolUsage) {
|
|
419
|
+
return branch.lastToolUsage;
|
|
420
|
+
}
|
|
421
|
+
const first = branchSessions[0];
|
|
422
|
+
if (!first) return null;
|
|
423
|
+
return {
|
|
424
|
+
branch: branch.name,
|
|
425
|
+
worktreePath: branch.worktreePath ?? null,
|
|
426
|
+
toolId:
|
|
427
|
+
first.toolType === "custom"
|
|
428
|
+
? first.toolName ?? "custom"
|
|
429
|
+
: (first.toolType as LastToolUsage["toolId"]),
|
|
430
|
+
toolLabel:
|
|
431
|
+
first.toolType === "custom"
|
|
432
|
+
? first.toolName ?? "Custom"
|
|
433
|
+
: toolLabel(first.toolType),
|
|
434
|
+
mode: first.mode ?? "normal",
|
|
435
|
+
model: null,
|
|
436
|
+
timestamp: first.startedAt ? Date.parse(first.startedAt) : Date.now(),
|
|
437
|
+
};
|
|
438
|
+
}, [branch?.lastToolUsage, branch?.name, branch?.worktreePath, branchSessions]);
|
|
439
|
+
|
|
413
440
|
const handleSessionExit = (code: number) => {
|
|
414
441
|
setActiveSessionId(null);
|
|
415
442
|
setIsTerminalFullscreen(false);
|
|
@@ -449,6 +476,21 @@ export function BranchDetailPage() {
|
|
|
449
476
|
{branch.worktreePath ? "Worktreeあり" : "Worktree未作成"}
|
|
450
477
|
</span>
|
|
451
478
|
</div>
|
|
479
|
+
<div className="badge-group" style={{ marginTop: "0.5rem" }}>
|
|
480
|
+
{latestToolUsage ? (
|
|
481
|
+
<>
|
|
482
|
+
<span className="status-badge status-badge--muted">
|
|
483
|
+
{renderToolUsage(latestToolUsage)}
|
|
484
|
+
</span>
|
|
485
|
+
<span className="status-badge status-badge--muted">
|
|
486
|
+
{formatUsageTimestamp(latestToolUsage.timestamp)} ・ worktree:{" "}
|
|
487
|
+
{latestToolUsage.worktreePath ?? branch.worktreePath ?? "N/A"}
|
|
488
|
+
</span>
|
|
489
|
+
</>
|
|
490
|
+
) : (
|
|
491
|
+
<span className="status-badge status-badge--muted">Unknown</span>
|
|
492
|
+
)}
|
|
493
|
+
</div>
|
|
452
494
|
<div className="page-hero__actions">
|
|
453
495
|
{!canStartSession ? (
|
|
454
496
|
<button
|
|
@@ -950,6 +992,32 @@ const SESSION_STATUS_LABEL: Record<
|
|
|
950
992
|
failed: "failed",
|
|
951
993
|
};
|
|
952
994
|
|
|
995
|
+
function renderToolUsage(usage: LastToolUsage): string {
|
|
996
|
+
const modeLabel =
|
|
997
|
+
usage.mode === "normal"
|
|
998
|
+
? "New"
|
|
999
|
+
: usage.mode === "continue"
|
|
1000
|
+
? "Continue"
|
|
1001
|
+
: usage.mode === "resume"
|
|
1002
|
+
? "Resume"
|
|
1003
|
+
: null;
|
|
1004
|
+
const toolText = mapToolLabel(usage.toolId, usage.toolLabel);
|
|
1005
|
+
return [toolText, modeLabel, usage.model].filter(Boolean).join(" | ");
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function formatUsageTimestamp(value: number): string {
|
|
1009
|
+
return formatDate(new Date(value).toISOString());
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function mapToolLabel(toolId: string, toolLabel?: string | null): string {
|
|
1013
|
+
if (toolId === "claude-code") return "Claude";
|
|
1014
|
+
if (toolId === "codex-cli") return "Codex";
|
|
1015
|
+
if (toolId === "gemini-cli") return "Gemini";
|
|
1016
|
+
if (toolId === "qwen-cli") return "Qwen";
|
|
1017
|
+
if (toolLabel) return toolLabel;
|
|
1018
|
+
return "Custom";
|
|
1019
|
+
}
|
|
1020
|
+
|
|
953
1021
|
function parseExtraArgs(value: string): string[] {
|
|
954
1022
|
return value
|
|
955
1023
|
.split(/\s+/)
|
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
AIToolSession,
|
|
12
12
|
StartSessionRequest,
|
|
13
13
|
} from "../../../types/api.js";
|
|
14
|
+
import { saveSession } from "../../../config/index.js";
|
|
15
|
+
import { execa } from "execa";
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* セッション関連のルートを登録
|
|
@@ -53,6 +55,37 @@ export async function registerSessionRoutes(
|
|
|
53
55
|
toolName,
|
|
54
56
|
);
|
|
55
57
|
|
|
58
|
+
// 履歴を永続化(best-effort)
|
|
59
|
+
try {
|
|
60
|
+
const { stdout: repoRoot } = await execa("git", ["rev-parse", "--show-toplevel"], {
|
|
61
|
+
cwd: worktreePath,
|
|
62
|
+
});
|
|
63
|
+
let branchName: string | null = null;
|
|
64
|
+
try {
|
|
65
|
+
const { stdout: branchStdout } = await execa(
|
|
66
|
+
"git",
|
|
67
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
68
|
+
{ cwd: worktreePath },
|
|
69
|
+
);
|
|
70
|
+
branchName = branchStdout.trim() || null;
|
|
71
|
+
} catch {
|
|
72
|
+
branchName = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await saveSession({
|
|
76
|
+
lastWorktreePath: worktreePath,
|
|
77
|
+
lastBranch: branchName,
|
|
78
|
+
lastUsedTool: toolType === "custom" ? toolName ?? "custom" : toolType,
|
|
79
|
+
toolLabel:
|
|
80
|
+
toolType === "custom" ? toolName ?? "Custom" : toolLabelFromType(toolType),
|
|
81
|
+
mode,
|
|
82
|
+
timestamp: Date.now(),
|
|
83
|
+
repositoryRoot: repoRoot.trim(),
|
|
84
|
+
});
|
|
85
|
+
} catch {
|
|
86
|
+
// ignore persistence errors
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
reply.code(201);
|
|
57
90
|
return { success: true, data: session };
|
|
58
91
|
} catch (error) {
|
|
@@ -128,3 +161,9 @@ export async function registerSessionRoutes(
|
|
|
128
161
|
}
|
|
129
162
|
});
|
|
130
163
|
}
|
|
164
|
+
|
|
165
|
+
function toolLabelFromType(toolType: "claude-code" | "codex-cli" | "custom") {
|
|
166
|
+
if (toolType === "claude-code") return "Claude";
|
|
167
|
+
if (toolType === "codex-cli") return "Codex";
|
|
168
|
+
return "Custom";
|
|
169
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { getPullRequestByBranch } from "../../../github.js";
|
|
17
17
|
import { listAdditionalWorktrees } from "../../../worktree.js";
|
|
18
18
|
import type { Branch, BranchSyncResult } from "../../../types/api.js";
|
|
19
|
+
import { getLastToolUsageMap } from "../../../config/index.js";
|
|
19
20
|
|
|
20
21
|
type DivergenceStatus = { remoteAhead: number; localAhead: number };
|
|
21
22
|
type DivergenceValue = NonNullable<NonNullable<Branch["divergence"]>>;
|
|
@@ -37,10 +38,22 @@ function mapPullRequestState(state: string): "open" | "merged" | "closed" {
|
|
|
37
38
|
* すべてのブランチ一覧を取得(マージステータスとWorktree情報付き)
|
|
38
39
|
*/
|
|
39
40
|
export async function listBranches(): Promise<Branch[]> {
|
|
40
|
-
const
|
|
41
|
+
const repoRoot = await getRepositoryRoot();
|
|
42
|
+
const lastToolUsageMap = await getLastToolUsageMap(repoRoot);
|
|
43
|
+
|
|
44
|
+
// リモートブランチの最新情報を取得(失敗してもローカル情報にはフォールバック)
|
|
45
|
+
try {
|
|
46
|
+
await fetchAllRemotes({ cwd: repoRoot });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn(
|
|
49
|
+
"Failed to fetch remote branches for Web UI; falling back to local branches",
|
|
50
|
+
error,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const [branches, worktrees] = await Promise.all([
|
|
41
55
|
getAllBranches(),
|
|
42
56
|
listAdditionalWorktrees(),
|
|
43
|
-
getRepositoryRoot(),
|
|
44
57
|
]);
|
|
45
58
|
const divergenceMap = await buildDivergenceMap(branches, repoRoot);
|
|
46
59
|
const upstreamMap = await collectUpstreamMap(repoRoot);
|
|
@@ -92,6 +105,8 @@ export async function listBranches(): Promise<Branch[]> {
|
|
|
92
105
|
? mapDivergence(divergenceStatus)
|
|
93
106
|
: null;
|
|
94
107
|
|
|
108
|
+
const lastToolUsage = lastToolUsageMap.get(branchInfo.name) ?? null;
|
|
109
|
+
|
|
95
110
|
return {
|
|
96
111
|
name: branchInfo.name,
|
|
97
112
|
type: branchInfo.type,
|
|
@@ -105,6 +120,7 @@ export async function listBranches(): Promise<Branch[]> {
|
|
|
105
120
|
baseBranch: baseBranch ?? null,
|
|
106
121
|
divergence,
|
|
107
122
|
prInfo,
|
|
123
|
+
...(lastToolUsage ? { lastToolUsage } : {}),
|
|
108
124
|
};
|
|
109
125
|
}),
|
|
110
126
|
);
|
package/src/worktree.ts
CHANGED
|
@@ -98,7 +98,13 @@ export interface WorktreeInfo {
|
|
|
98
98
|
|
|
99
99
|
async function listWorktrees(): Promise<WorktreeInfo[]> {
|
|
100
100
|
try {
|
|
101
|
-
const {
|
|
101
|
+
const { getRepositoryRoot } = await import("./git.js");
|
|
102
|
+
const repoRoot = await getRepositoryRoot();
|
|
103
|
+
const { stdout } = await execa(
|
|
104
|
+
"git",
|
|
105
|
+
["worktree", "list", "--porcelain"],
|
|
106
|
+
{ cwd: repoRoot },
|
|
107
|
+
);
|
|
102
108
|
const worktrees: WorktreeInfo[] = [];
|
|
103
109
|
const lines = stdout.split("\n");
|
|
104
110
|
|