@akiojin/gwt 2.13.0 → 3.0.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 (295) hide show
  1. package/README.ja.md +41 -0
  2. package/README.md +38 -0
  3. package/dist/claude.d.ts.map +1 -1
  4. package/dist/claude.js +17 -11
  5. package/dist/claude.js.map +1 -1
  6. package/dist/cli/ui/components/App.d.ts +0 -6
  7. package/dist/cli/ui/components/App.d.ts.map +1 -1
  8. package/dist/cli/ui/components/App.js +27 -88
  9. package/dist/cli/ui/components/App.js.map +1 -1
  10. package/dist/cli/ui/components/common/Select.js +2 -2
  11. package/dist/cli/ui/components/common/Select.js.map +1 -1
  12. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  13. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  14. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
  15. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +3 -3
  16. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
  17. package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
  18. package/dist/cli/ui/utils/continueSession.js +1 -1
  19. package/dist/cli/ui/utils/continueSession.js.map +1 -1
  20. package/dist/client/assets/index-DsDNCy5f.css +1 -0
  21. package/dist/client/assets/index-f5D2XwDh.js +72 -0
  22. package/dist/client/index.html +2 -2
  23. package/dist/codex.d.ts.map +1 -1
  24. package/dist/codex.js +21 -11
  25. package/dist/codex.js.map +1 -1
  26. package/dist/config/builtin-tools.d.ts.map +1 -1
  27. package/dist/config/builtin-tools.js +3 -2
  28. package/dist/config/builtin-tools.js.map +1 -1
  29. package/dist/config/shared-env.d.ts +41 -0
  30. package/dist/config/shared-env.d.ts.map +1 -0
  31. package/dist/config/shared-env.js +114 -0
  32. package/dist/config/shared-env.js.map +1 -0
  33. package/dist/gemini.d.ts.map +1 -1
  34. package/dist/gemini.js +20 -17
  35. package/dist/gemini.js.map +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +13 -8
  38. package/dist/index.js.map +1 -1
  39. package/dist/logging/logger.d.ts.map +1 -1
  40. package/dist/logging/logger.js +4 -1
  41. package/dist/logging/logger.js.map +1 -1
  42. package/dist/qwen.d.ts.map +1 -1
  43. package/dist/qwen.js +15 -11
  44. package/dist/qwen.js.map +1 -1
  45. package/dist/services/aiToolResolver.d.ts +41 -0
  46. package/dist/services/aiToolResolver.d.ts.map +1 -0
  47. package/dist/services/aiToolResolver.js +194 -0
  48. package/dist/services/aiToolResolver.js.map +1 -0
  49. package/dist/services/customToolResolver.d.ts +10 -0
  50. package/dist/services/customToolResolver.d.ts.map +1 -0
  51. package/dist/services/customToolResolver.js +71 -0
  52. package/dist/services/customToolResolver.js.map +1 -0
  53. package/dist/shared/aiToolConstants.d.ts +9 -0
  54. package/dist/shared/aiToolConstants.d.ts.map +1 -0
  55. package/dist/shared/aiToolConstants.js +29 -0
  56. package/dist/shared/aiToolConstants.js.map +1 -0
  57. package/dist/types/tools.d.ts +12 -0
  58. package/dist/types/tools.d.ts.map +1 -1
  59. package/dist/utils/prompt.d.ts.map +1 -1
  60. package/dist/utils/prompt.js.map +1 -1
  61. package/dist/utils/session.d.ts.map +1 -1
  62. package/dist/utils/session.js +15 -6
  63. package/dist/utils/session.js.map +1 -1
  64. package/dist/utils/terminal.d.ts +12 -3
  65. package/dist/utils/terminal.d.ts.map +1 -1
  66. package/dist/utils/terminal.js +5 -34
  67. package/dist/utils/terminal.js.map +1 -1
  68. package/dist/utils/webui.d.ts +8 -0
  69. package/dist/utils/webui.d.ts.map +1 -0
  70. package/dist/utils/webui.js +35 -0
  71. package/dist/utils/webui.js.map +1 -0
  72. package/dist/web/client/src/components/AIToolLaunchModal.d.ts +9 -0
  73. package/dist/web/client/src/components/AIToolLaunchModal.d.ts.map +1 -0
  74. package/dist/web/client/src/components/AIToolLaunchModal.js +363 -0
  75. package/dist/web/client/src/components/AIToolLaunchModal.js.map +1 -0
  76. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  77. package/dist/web/client/src/components/BranchGraph.js +46 -49
  78. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  79. package/dist/web/client/src/components/CustomToolForm.d.ts +23 -0
  80. package/dist/web/client/src/components/CustomToolForm.d.ts.map +1 -0
  81. package/dist/web/client/src/components/CustomToolForm.js +209 -0
  82. package/dist/web/client/src/components/CustomToolForm.js.map +1 -0
  83. package/dist/web/client/src/components/CustomToolList.d.ts +10 -0
  84. package/dist/web/client/src/components/CustomToolList.d.ts.map +1 -0
  85. package/dist/web/client/src/components/CustomToolList.js +57 -0
  86. package/dist/web/client/src/components/CustomToolList.js.map +1 -0
  87. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  88. package/dist/web/client/src/components/EnvEditor.js +33 -26
  89. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  90. package/dist/web/client/src/components/EnvironmentEditor.d.ts +17 -0
  91. package/dist/web/client/src/components/EnvironmentEditor.d.ts.map +1 -0
  92. package/dist/web/client/src/components/EnvironmentEditor.js +22 -0
  93. package/dist/web/client/src/components/EnvironmentEditor.js.map +1 -0
  94. package/dist/web/client/src/components/Terminal.d.ts.map +1 -1
  95. package/dist/web/client/src/components/Terminal.js +10 -3
  96. package/dist/web/client/src/components/Terminal.js.map +1 -1
  97. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts +10 -0
  98. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -0
  99. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +104 -0
  100. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -0
  101. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts +22 -0
  102. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts.map +1 -0
  103. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js +79 -0
  104. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js.map +1 -0
  105. package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts +11 -0
  106. package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts.map +1 -0
  107. package/dist/web/client/src/components/branch-detail/TerminalPanel.js +32 -0
  108. package/dist/web/client/src/components/branch-detail/TerminalPanel.js.map +1 -0
  109. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts +40 -0
  110. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts.map +1 -0
  111. package/dist/web/client/src/components/branch-detail/ToolLauncher.js +147 -0
  112. package/dist/web/client/src/components/branch-detail/ToolLauncher.js.map +1 -0
  113. package/dist/web/client/src/components/branch-detail/index.d.ts +5 -0
  114. package/dist/web/client/src/components/branch-detail/index.d.ts.map +1 -0
  115. package/dist/web/client/src/components/branch-detail/index.js +5 -0
  116. package/dist/web/client/src/components/branch-detail/index.js.map +1 -0
  117. package/dist/web/client/src/components/common/BranchCard.d.ts +17 -0
  118. package/dist/web/client/src/components/common/BranchCard.d.ts.map +1 -0
  119. package/dist/web/client/src/components/common/BranchCard.js +36 -0
  120. package/dist/web/client/src/components/common/BranchCard.js.map +1 -0
  121. package/dist/web/client/src/components/common/MetricCard.d.ts +10 -0
  122. package/dist/web/client/src/components/common/MetricCard.d.ts.map +1 -0
  123. package/dist/web/client/src/components/common/MetricCard.js +10 -0
  124. package/dist/web/client/src/components/common/MetricCard.js.map +1 -0
  125. package/dist/web/client/src/components/common/PageHeader.d.ts +12 -0
  126. package/dist/web/client/src/components/common/PageHeader.d.ts.map +1 -0
  127. package/dist/web/client/src/components/common/PageHeader.js +14 -0
  128. package/dist/web/client/src/components/common/PageHeader.js.map +1 -0
  129. package/dist/web/client/src/components/common/SearchInput.d.ts +14 -0
  130. package/dist/web/client/src/components/common/SearchInput.d.ts.map +1 -0
  131. package/dist/web/client/src/components/common/SearchInput.js +15 -0
  132. package/dist/web/client/src/components/common/SearchInput.js.map +1 -0
  133. package/dist/web/client/src/components/common/StatusBadge.d.ts +10 -0
  134. package/dist/web/client/src/components/common/StatusBadge.d.ts.map +1 -0
  135. package/dist/web/client/src/components/common/StatusBadge.js +15 -0
  136. package/dist/web/client/src/components/common/StatusBadge.js.map +1 -0
  137. package/dist/web/client/src/components/common/index.d.ts +6 -0
  138. package/dist/web/client/src/components/common/index.d.ts.map +1 -0
  139. package/dist/web/client/src/components/common/index.js +6 -0
  140. package/dist/web/client/src/components/common/index.js.map +1 -0
  141. package/dist/web/client/src/components/ui/alert.d.ts +9 -0
  142. package/dist/web/client/src/components/ui/alert.d.ts.map +1 -0
  143. package/dist/web/client/src/components/ui/alert.js +25 -0
  144. package/dist/web/client/src/components/ui/alert.js.map +1 -0
  145. package/dist/web/client/src/components/ui/badge.d.ts +10 -0
  146. package/dist/web/client/src/components/ui/badge.d.ts.map +1 -0
  147. package/dist/web/client/src/components/ui/badge.js +25 -0
  148. package/dist/web/client/src/components/ui/badge.js.map +1 -0
  149. package/dist/web/client/src/components/ui/button.d.ts +12 -0
  150. package/dist/web/client/src/components/ui/button.d.ts.map +1 -0
  151. package/dist/web/client/src/components/ui/button.js +33 -0
  152. package/dist/web/client/src/components/ui/button.js.map +1 -0
  153. package/dist/web/client/src/components/ui/card.d.ts +9 -0
  154. package/dist/web/client/src/components/ui/card.d.ts.map +1 -0
  155. package/dist/web/client/src/components/ui/card.js +16 -0
  156. package/dist/web/client/src/components/ui/card.js.map +1 -0
  157. package/dist/web/client/src/components/ui/index.d.ts +8 -0
  158. package/dist/web/client/src/components/ui/index.d.ts.map +1 -0
  159. package/dist/web/client/src/components/ui/index.js +8 -0
  160. package/dist/web/client/src/components/ui/index.js.map +1 -0
  161. package/dist/web/client/src/components/ui/input.d.ts +4 -0
  162. package/dist/web/client/src/components/ui/input.d.ts.map +1 -0
  163. package/dist/web/client/src/components/ui/input.js +8 -0
  164. package/dist/web/client/src/components/ui/input.js.map +1 -0
  165. package/dist/web/client/src/components/ui/select.d.ts +14 -0
  166. package/dist/web/client/src/components/ui/select.d.ts.map +1 -0
  167. package/dist/web/client/src/components/ui/select.js +39 -0
  168. package/dist/web/client/src/components/ui/select.js.map +1 -0
  169. package/dist/web/client/src/components/ui/table.d.ts +11 -0
  170. package/dist/web/client/src/components/ui/table.d.ts.map +1 -0
  171. package/dist/web/client/src/components/ui/table.js +21 -0
  172. package/dist/web/client/src/components/ui/table.js.map +1 -0
  173. package/dist/web/client/src/hooks/useSessions.d.ts.map +1 -1
  174. package/dist/web/client/src/hooks/useSessions.js +6 -1
  175. package/dist/web/client/src/hooks/useSessions.js.map +1 -1
  176. package/dist/web/client/src/lib/utils.d.ts +7 -0
  177. package/dist/web/client/src/lib/utils.d.ts.map +1 -0
  178. package/dist/web/client/src/lib/utils.js +10 -0
  179. package/dist/web/client/src/lib/utils.js.map +1 -0
  180. package/dist/web/client/src/lib/websocket.d.ts +7 -0
  181. package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
  182. package/dist/web/client/src/lib/websocket.js +44 -0
  183. package/dist/web/client/src/lib/websocket.js.map +1 -1
  184. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  185. package/dist/web/client/src/pages/BranchDetailPage.js +125 -376
  186. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  187. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  188. package/dist/web/client/src/pages/BranchListPage.js +89 -127
  189. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  190. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  191. package/dist/web/client/src/pages/ConfigManagementPage.js +46 -41
  192. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  193. package/dist/web/client/src/pages/ConfigPage.d.ts +3 -0
  194. package/dist/web/client/src/pages/ConfigPage.d.ts.map +1 -0
  195. package/dist/web/client/src/pages/ConfigPage.js +216 -0
  196. package/dist/web/client/src/pages/ConfigPage.js.map +1 -0
  197. package/dist/web/client/vite.config.d.ts.map +1 -1
  198. package/dist/web/client/vite.config.js +8 -1
  199. package/dist/web/client/vite.config.js.map +1 -1
  200. package/dist/web/server/index.d.ts +24 -2
  201. package/dist/web/server/index.d.ts.map +1 -1
  202. package/dist/web/server/index.js +46 -15
  203. package/dist/web/server/index.js.map +1 -1
  204. package/dist/web/server/pty/manager.d.ts +12 -10
  205. package/dist/web/server/pty/manager.d.ts.map +1 -1
  206. package/dist/web/server/pty/manager.js +76 -43
  207. package/dist/web/server/pty/manager.js.map +1 -1
  208. package/dist/web/server/routes/sessions.d.ts.map +1 -1
  209. package/dist/web/server/routes/sessions.js +35 -2
  210. package/dist/web/server/routes/sessions.js.map +1 -1
  211. package/dist/web/server/routes/worktrees.js +2 -2
  212. package/dist/web/server/routes/worktrees.js.map +1 -1
  213. package/dist/web/server/services/worktrees.d.ts.map +1 -1
  214. package/dist/web/server/services/worktrees.js +7 -1
  215. package/dist/web/server/services/worktrees.js.map +1 -1
  216. package/dist/web/server/tray.d.ts +25 -0
  217. package/dist/web/server/tray.d.ts.map +1 -0
  218. package/dist/web/server/tray.js +98 -0
  219. package/dist/web/server/tray.js.map +1 -0
  220. package/dist/web/server/websocket/handler.d.ts +18 -2
  221. package/dist/web/server/websocket/handler.d.ts.map +1 -1
  222. package/dist/web/server/websocket/handler.js +82 -9
  223. package/dist/web/server/websocket/handler.js.map +1 -1
  224. package/package.json +15 -2
  225. package/src/claude.ts +26 -15
  226. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +1 -1
  227. package/src/cli/ui/__tests__/components/common/Select.test.tsx +11 -0
  228. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +3 -1
  229. package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +4 -4
  230. package/src/cli/ui/components/App.tsx +33 -133
  231. package/src/cli/ui/components/common/Select.tsx +2 -2
  232. package/src/cli/ui/components/screens/BranchListScreen.tsx +28 -21
  233. package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +41 -46
  234. package/src/cli/ui/utils/continueSession.ts +1 -7
  235. package/src/codex.ts +31 -22
  236. package/src/config/builtin-tools.ts +6 -2
  237. package/src/config/shared-env.ts +139 -0
  238. package/src/gemini.ts +35 -22
  239. package/src/index.ts +13 -8
  240. package/src/logging/logger.ts +5 -2
  241. package/src/qwen.ts +28 -19
  242. package/src/services/aiToolResolver.ts +276 -0
  243. package/src/services/customToolResolver.ts +98 -0
  244. package/src/shared/aiToolConstants.ts +30 -0
  245. package/src/trayicon.d.ts +30 -0
  246. package/src/types/tools.ts +15 -0
  247. package/src/utils/prompt.ts +15 -9
  248. package/src/utils/session.ts +80 -26
  249. package/src/utils/terminal.ts +11 -41
  250. package/src/utils/webui.ts +43 -0
  251. package/src/web/client/components.json +21 -0
  252. package/src/web/client/src/components/AIToolLaunchModal.tsx +575 -0
  253. package/src/web/client/src/components/BranchGraph.tsx +95 -75
  254. package/src/web/client/src/components/CustomToolForm.tsx +386 -0
  255. package/src/web/client/src/components/CustomToolList.tsx +119 -0
  256. package/src/web/client/src/components/EnvEditor.tsx +91 -81
  257. package/src/web/client/src/components/EnvironmentEditor.tsx +97 -0
  258. package/src/web/client/src/components/Terminal.tsx +11 -3
  259. package/src/web/client/src/components/branch-detail/BranchInfoCards.tsx +179 -0
  260. package/src/web/client/src/components/branch-detail/SessionHistoryTable.tsx +181 -0
  261. package/src/web/client/src/components/branch-detail/TerminalPanel.tsx +92 -0
  262. package/src/web/client/src/components/branch-detail/ToolLauncher.tsx +327 -0
  263. package/src/web/client/src/components/branch-detail/index.ts +4 -0
  264. package/src/web/client/src/components/common/BranchCard.tsx +117 -0
  265. package/src/web/client/src/components/common/MetricCard.tsx +22 -0
  266. package/src/web/client/src/components/common/PageHeader.tsx +44 -0
  267. package/src/web/client/src/components/common/SearchInput.tsx +40 -0
  268. package/src/web/client/src/components/common/StatusBadge.tsx +37 -0
  269. package/src/web/client/src/components/common/index.ts +5 -0
  270. package/src/web/client/src/components/ui/alert.tsx +63 -0
  271. package/src/web/client/src/components/ui/badge.tsx +44 -0
  272. package/src/web/client/src/components/ui/button.tsx +57 -0
  273. package/src/web/client/src/components/ui/card.tsx +82 -0
  274. package/src/web/client/src/components/ui/index.ts +32 -0
  275. package/src/web/client/src/components/ui/input.tsx +21 -0
  276. package/src/web/client/src/components/ui/select.tsx +156 -0
  277. package/src/web/client/src/components/ui/table.tsx +119 -0
  278. package/src/web/client/src/hooks/useSessions.ts +10 -1
  279. package/src/web/client/src/index.css +46 -816
  280. package/src/web/client/src/lib/utils.ts +10 -0
  281. package/src/web/client/src/lib/websocket.ts +48 -1
  282. package/src/web/client/src/pages/BranchDetailPage.tsx +247 -723
  283. package/src/web/client/src/pages/BranchListPage.tsx +190 -236
  284. package/src/web/client/src/pages/ConfigManagementPage.tsx +94 -76
  285. package/src/web/client/src/pages/ConfigPage.tsx +362 -0
  286. package/src/web/client/vite.config.ts +8 -1
  287. package/src/web/server/index.ts +72 -15
  288. package/src/web/server/pty/manager.ts +128 -55
  289. package/src/web/server/routes/sessions.ts +59 -7
  290. package/src/web/server/routes/worktrees.ts +3 -3
  291. package/src/web/server/services/worktrees.ts +12 -4
  292. package/src/web/server/tray.ts +120 -0
  293. package/src/web/server/websocket/handler.ts +119 -13
  294. package/dist/client/assets/index-DeNwPosA.css +0 -1
  295. package/dist/client/assets/index-Dl798X5w.js +0 -32
@@ -1,5 +1,8 @@
1
1
  import React, { useMemo } from "react";
2
2
  import { Link } from "react-router-dom";
3
+ import { Card, CardHeader, CardContent } from "@/components/ui/card";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { cn } from "@/lib/utils";
3
6
  import type { Branch } from "../../../../types/api.js";
4
7
 
5
8
  const UNKNOWN_BASE = "__unknown__";
@@ -55,7 +58,6 @@ export function BranchGraph({ branches }: BranchGraphProps) {
55
58
  const base = branch.baseBranch ?? UNKNOWN_BASE;
56
59
 
57
60
  if (!branch.baseBranch && referencedBases.has(branch.name)) {
58
- // ベースとして参照されている場合は、グラフ上で基点ノードとしてのみ表示
59
61
  return;
60
62
  }
61
63
 
@@ -75,71 +77,83 @@ export function BranchGraph({ branches }: BranchGraphProps) {
75
77
  });
76
78
 
77
79
  return Array.from(laneMap.values()).sort((a, b) => {
78
- if (a.id === UNKNOWN_BASE) {
79
- return 1;
80
- }
81
- if (b.id === UNKNOWN_BASE) {
82
- return -1;
83
- }
80
+ if (a.id === UNKNOWN_BASE) return 1;
81
+ if (b.id === UNKNOWN_BASE) return -1;
84
82
  return a.baseLabel.localeCompare(b.baseLabel, "ja");
85
83
  });
86
84
  }, [branches, branchMap, referencedBases]);
87
85
 
88
86
  if (!lanes.length) {
89
87
  return (
90
- <section className="branch-graph-panel">
91
- <div className="branch-graph-panel__empty">
92
- <p>グラフ表示できるブランチがありません。</p>
93
- <p>fetch済みのブランチやWorktreeを追加すると関係図が表示されます。</p>
94
- </div>
95
- </section>
88
+ <Card className="border-dashed">
89
+ <CardContent className="flex flex-col items-center justify-center py-12 text-center">
90
+ <p className="text-muted-foreground">
91
+ グラフ表示できるブランチがありません。
92
+ </p>
93
+ <p className="text-sm text-muted-foreground">
94
+ fetch済みのブランチやWorktreeを追加すると関係図が表示されます。
95
+ </p>
96
+ </CardContent>
97
+ </Card>
96
98
  );
97
99
  }
98
100
 
99
101
  return (
100
- <section className="branch-graph-panel">
101
- <header className="branch-graph-panel__header">
102
- <div>
103
- <p className="branch-graph-panel__eyebrow">BRANCH GRAPH</p>
104
- <h2>ベースブランチの関係をグラフィカルに把握</h2>
105
- <p>
106
- baseRef、Git
107
- upstream、merge-baseヒューリスティクスを用いて推定したベースブランチ単位で
108
- 派生ノードをレーン表示します。
109
- </p>
102
+ <Card>
103
+ <CardHeader className="pb-4">
104
+ <div className="flex flex-wrap items-start justify-between gap-4">
105
+ <div>
106
+ <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
107
+ BRANCH GRAPH
108
+ </p>
109
+ <h2 className="mt-1 text-lg font-semibold">
110
+ ベースブランチの関係をグラフィカルに把握
111
+ </h2>
112
+ <p className="mt-1 text-sm text-muted-foreground">
113
+ baseRef、Git
114
+ upstream、merge-baseヒューリスティクスを用いて推定したベースブランチ単位で派生ノードをレーン表示します。
115
+ </p>
116
+ </div>
117
+ <div className="flex flex-wrap gap-2">
118
+ <Badge variant="outline">Base</Badge>
119
+ <Badge variant="local">Local</Badge>
120
+ <Badge variant="remote">Remote</Badge>
121
+ <Badge variant="success">Worktree</Badge>
122
+ </div>
110
123
  </div>
111
- <div className="branch-graph-panel__legend">
112
- <span className="graph-chip graph-chip--base">Base</span>
113
- <span className="graph-chip graph-chip--local">Local</span>
114
- <span className="graph-chip graph-chip--remote">Remote</span>
115
- <span className="graph-chip graph-chip--worktree">Worktree</span>
116
- </div>
117
- </header>
124
+ </CardHeader>
118
125
 
119
- <div className="branch-graph">
126
+ <CardContent className="space-y-4">
120
127
  {lanes.map((lane) => (
121
- <article className="branch-graph__lane" key={lane.id}>
122
- <div className="branch-graph__lane-heading">
123
- <p className="branch-graph__lane-label">
124
- {lane.baseLabel}
128
+ <article key={lane.id} className="rounded-lg border bg-muted/30 p-4">
129
+ <div className="mb-3 flex items-center justify-between">
130
+ <div className="flex items-center gap-2">
131
+ <span className="font-semibold">{lane.baseLabel}</span>
125
132
  {lane.baseNode && (
126
- <span className="branch-graph__lane-meta">
133
+ <Badge
134
+ variant={
135
+ lane.baseNode.type === "local" ? "local" : "remote"
136
+ }
137
+ className="text-xs"
138
+ >
127
139
  {lane.baseNode.type === "local" ? "LOCAL" : "REMOTE"}
128
- </span>
140
+ </Badge>
129
141
  )}
130
142
  {lane.isSyntheticBase && (
131
- <span className="branch-graph__lane-meta lane-meta--muted">
143
+ <Badge
144
+ variant="outline"
145
+ className="text-xs text-muted-foreground"
146
+ >
132
147
  推定のみ
133
- </span>
148
+ </Badge>
134
149
  )}
135
- </p>
136
- <span className="branch-graph__lane-count">
137
- {lane.nodes.length} branch
138
- {lane.nodes.length > 1 ? "es" : ""}
150
+ </div>
151
+ <span className="text-sm text-muted-foreground">
152
+ {lane.nodes.length} branch{lane.nodes.length > 1 ? "es" : ""}
139
153
  </span>
140
154
  </div>
141
155
 
142
- <div className="branch-graph__track">
156
+ <div className="flex flex-wrap gap-2">
143
157
  {renderBaseNode(lane)}
144
158
  {lane.nodes.map((branch) => (
145
159
  <BranchNode key={branch.name} branch={branch} />
@@ -147,25 +161,30 @@ export function BranchGraph({ branches }: BranchGraphProps) {
147
161
  </div>
148
162
  </article>
149
163
  ))}
150
- </div>
151
- </section>
164
+ </CardContent>
165
+ </Card>
152
166
  );
153
167
  }
154
168
 
155
169
  function renderBaseNode(lane: Lane) {
156
170
  const label =
157
171
  lane.baseLabel === "ベース不明" ? "Unknown base" : lane.baseLabel;
172
+
158
173
  const content = (
159
174
  <div
160
- className={`branch-graph__node branch-graph__node--base ${
161
- lane.baseNode ? `branch-graph__node--${lane.baseNode.type}` : ""
162
- }`}
175
+ className={cn(
176
+ "group relative rounded-md border bg-card px-3 py-2 transition-colors hover:border-muted-foreground/50",
177
+ lane.baseNode?.type === "local" && "border-l-2 border-l-local",
178
+ lane.baseNode?.type === "remote" && "border-l-2 border-l-remote",
179
+ )}
163
180
  >
164
- <span className="branch-graph__node-label">{label}</span>
165
- <span className="branch-graph__node-meta">BASE</span>
166
- <div className="branch-graph__tooltip">
167
- <p>{label}</p>
168
- <p>
181
+ <span className="block truncate text-sm font-medium">{label}</span>
182
+ <span className="text-xs text-muted-foreground">BASE</span>
183
+
184
+ {/* Tooltip */}
185
+ <div className="invisible absolute bottom-full left-0 z-10 mb-2 w-48 rounded-md border bg-popover p-2 text-xs shadow-md group-hover:visible">
186
+ <p className="font-medium">{label}</p>
187
+ <p className="text-muted-foreground">
169
188
  {lane.baseNode
170
189
  ? `type: ${lane.baseNode.type}`
171
190
  : "推定されたベースブランチ"}
@@ -179,7 +198,7 @@ function renderBaseNode(lane: Lane) {
179
198
  <Link
180
199
  key={`base-${lane.id}`}
181
200
  to={`/${encodeURIComponent(lane.baseNode.name)}`}
182
- className="branch-graph__node-link"
201
+ className="block"
183
202
  aria-label={`ベースブランチ ${lane.baseNode.name} を開く`}
184
203
  >
185
204
  {content}
@@ -187,35 +206,36 @@ function renderBaseNode(lane: Lane) {
187
206
  );
188
207
  }
189
208
 
190
- return (
191
- <div key={`base-${lane.id}`} className="branch-graph__node-link">
192
- {content}
193
- </div>
194
- );
209
+ return <div key={`base-${lane.id}`}>{content}</div>;
195
210
  }
196
211
 
197
212
  function BranchNode({ branch }: { branch: Branch }) {
198
213
  const node = (
199
214
  <div
200
- className={`branch-graph__node branch-graph__node--${branch.type} ${
201
- branch.mergeStatus === "merged"
202
- ? "branch-graph__node--merged"
203
- : branch.mergeStatus === "unmerged"
204
- ? "branch-graph__node--active"
205
- : ""
206
- }`}
215
+ className={cn(
216
+ "group relative rounded-md border bg-card px-3 py-2 transition-colors hover:border-muted-foreground/50",
217
+ branch.type === "local" && "border-l-2 border-l-local",
218
+ branch.type === "remote" && "border-l-2 border-l-remote",
219
+ branch.mergeStatus === "merged" && "opacity-60",
220
+ )}
207
221
  >
208
- <span className="branch-graph__node-label">
222
+ <span className="block truncate text-sm font-medium">
209
223
  {formatBranchLabel(branch)}
210
224
  </span>
211
- <span className="branch-graph__node-meta">
225
+ <span className="text-xs text-muted-foreground">
212
226
  {branch.worktreePath ? "Worktree" : "No Worktree"}
213
227
  </span>
214
- <div className="branch-graph__tooltip">
215
- <p>{branch.name}</p>
216
- <p>base: {branch.baseBranch ?? "unknown"}</p>
217
- <p>{getDivergenceLabel(branch)}</p>
218
- <p>{branch.worktreePath ?? "Worktree未作成"}</p>
228
+
229
+ {/* Tooltip */}
230
+ <div className="invisible absolute bottom-full left-0 z-10 mb-2 w-56 rounded-md border bg-popover p-2 text-xs shadow-md group-hover:visible">
231
+ <p className="font-medium">{branch.name}</p>
232
+ <p className="text-muted-foreground">
233
+ base: {branch.baseBranch ?? "unknown"}
234
+ </p>
235
+ <p className="text-muted-foreground">{getDivergenceLabel(branch)}</p>
236
+ <p className="text-muted-foreground">
237
+ {branch.worktreePath ?? "Worktree未作成"}
238
+ </p>
219
239
  </div>
220
240
  </div>
221
241
  );
@@ -223,7 +243,7 @@ function BranchNode({ branch }: { branch: Branch }) {
223
243
  return (
224
244
  <Link
225
245
  to={`/${encodeURIComponent(branch.name)}`}
226
- className="branch-graph__node-link"
246
+ className="block"
227
247
  aria-label={`${branch.name} の詳細を開く`}
228
248
  >
229
249
  {node}
@@ -0,0 +1,386 @@
1
+ import React, { useState } from "react";
2
+ import { Button } from "@/components/ui/button";
3
+ import { Input } from "@/components/ui/input";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@/components/ui/select";
11
+ import type {
12
+ CustomAITool,
13
+ EnvironmentVariable,
14
+ } from "../../../../types/api.js";
15
+
16
+ export interface CustomToolFormValue {
17
+ id: string;
18
+ displayName: string;
19
+ icon?: string | null;
20
+ description?: string | null;
21
+ executionType: CustomAITool["executionType"];
22
+ command: string;
23
+ defaultArgs?: string[] | null;
24
+ modeArgs: CustomAITool["modeArgs"];
25
+ permissionSkipArgs?: string[] | null;
26
+ env?: EnvironmentVariable[] | null;
27
+ }
28
+
29
+ interface CustomToolFormProps {
30
+ initialValue?: CustomAITool;
31
+ onSubmit: (value: CustomToolFormValue) => void;
32
+ onCancel: () => void;
33
+ isSaving?: boolean;
34
+ }
35
+
36
+ interface FormErrors {
37
+ id?: string;
38
+ displayName?: string;
39
+ command?: string;
40
+ env?: string;
41
+ }
42
+
43
+ export function CustomToolForm({
44
+ initialValue,
45
+ onSubmit,
46
+ onCancel,
47
+ isSaving,
48
+ }: CustomToolFormProps) {
49
+ const [formState, setFormState] = useState(() =>
50
+ createInitialState(initialValue),
51
+ );
52
+ const [errors, setErrors] = useState<FormErrors>({});
53
+
54
+ const title = initialValue ? "ツールを編集" : "新規カスタムツール";
55
+
56
+ const handleChange =
57
+ (field: keyof typeof formState) =>
58
+ (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
59
+ setFormState((prev) => ({ ...prev, [field]: event.target.value }));
60
+ };
61
+
62
+ const handleSelectChange =
63
+ (field: keyof typeof formState) => (value: string) => {
64
+ setFormState((prev) => ({ ...prev, [field]: value }));
65
+ };
66
+
67
+ const handleSubmit = (event: React.FormEvent) => {
68
+ event.preventDefault();
69
+ const nextErrors: FormErrors = {};
70
+
71
+ if (!formState.id.trim()) {
72
+ nextErrors.id = "IDは必須です";
73
+ }
74
+ if (!formState.displayName.trim()) {
75
+ nextErrors.displayName = "表示名は必須です";
76
+ }
77
+ if (!formState.command.trim()) {
78
+ nextErrors.command = "コマンド/パッケージ名は必須です";
79
+ }
80
+
81
+ const envResult = parseEnv(formState.env);
82
+ let parsedEnv: EnvironmentVariable[] | null = null;
83
+ if (envResult instanceof Error) {
84
+ nextErrors.env = envResult.message;
85
+ } else {
86
+ parsedEnv = envResult;
87
+ }
88
+
89
+ if (Object.keys(nextErrors).length) {
90
+ setErrors(nextErrors);
91
+ return;
92
+ }
93
+
94
+ setErrors({});
95
+ onSubmit({
96
+ id: formState.id.trim(),
97
+ displayName: formState.displayName.trim(),
98
+ icon: formState.icon?.trim() ? formState.icon.trim() : null,
99
+ description: formState.description?.trim()
100
+ ? formState.description.trim()
101
+ : null,
102
+ executionType: formState.executionType,
103
+ command: formState.command.trim(),
104
+ defaultArgs: parseList(formState.defaultArgs),
105
+ modeArgs: {
106
+ normal: parseList(formState.modeNormal) ?? [],
107
+ continue: parseList(formState.modeContinue) ?? [],
108
+ resume: parseList(formState.modeResume) ?? [],
109
+ },
110
+ permissionSkipArgs: parseList(formState.permissionSkipArgs),
111
+ env: parsedEnv,
112
+ });
113
+ };
114
+
115
+ return (
116
+ <form onSubmit={handleSubmit} className="space-y-6">
117
+ <div className="flex items-start justify-between gap-4">
118
+ <div>
119
+ <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
120
+ {title}
121
+ </p>
122
+ <h3 className="mt-1 text-lg font-semibold">
123
+ {formState.displayName || "カスタムAIツール"}
124
+ </h3>
125
+ </div>
126
+ <div className="flex gap-2">
127
+ <Button
128
+ type="button"
129
+ variant="ghost"
130
+ onClick={onCancel}
131
+ disabled={isSaving}
132
+ >
133
+ キャンセル
134
+ </Button>
135
+ <Button type="submit" disabled={isSaving}>
136
+ {isSaving ? "保存中..." : "保存"}
137
+ </Button>
138
+ </div>
139
+ </div>
140
+
141
+ <div className="grid gap-4 sm:grid-cols-2">
142
+ <div className="space-y-2">
143
+ <label className="text-sm font-medium">ツールID *</label>
144
+ <Input
145
+ type="text"
146
+ value={formState.id}
147
+ onChange={handleChange("id")}
148
+ disabled={Boolean(initialValue)}
149
+ />
150
+ {errors.id && <p className="text-xs text-destructive">{errors.id}</p>}
151
+ </div>
152
+
153
+ <div className="space-y-2">
154
+ <label className="text-sm font-medium">表示名 *</label>
155
+ <Input
156
+ type="text"
157
+ value={formState.displayName}
158
+ onChange={handleChange("displayName")}
159
+ />
160
+ {errors.displayName && (
161
+ <p className="text-xs text-destructive">{errors.displayName}</p>
162
+ )}
163
+ </div>
164
+
165
+ <div className="space-y-2">
166
+ <label className="text-sm font-medium">アイコン (任意)</label>
167
+ <Input
168
+ type="text"
169
+ value={formState.icon}
170
+ onChange={handleChange("icon")}
171
+ maxLength={2}
172
+ />
173
+ </div>
174
+
175
+ <div className="space-y-2">
176
+ <label className="text-sm font-medium">説明 (任意)</label>
177
+ <Input
178
+ type="text"
179
+ value={formState.description}
180
+ onChange={handleChange("description")}
181
+ />
182
+ </div>
183
+
184
+ <div className="space-y-2">
185
+ <label className="text-sm font-medium">実行タイプ *</label>
186
+ <Select
187
+ value={formState.executionType}
188
+ onValueChange={handleSelectChange("executionType")}
189
+ disabled={Boolean(initialValue)}
190
+ >
191
+ <SelectTrigger>
192
+ <SelectValue />
193
+ </SelectTrigger>
194
+ <SelectContent>
195
+ <SelectItem value="path">path (絶対パス)</SelectItem>
196
+ <SelectItem value="bunx">bunx (パッケージ)</SelectItem>
197
+ <SelectItem value="command">command (PATH)</SelectItem>
198
+ </SelectContent>
199
+ </Select>
200
+ </div>
201
+
202
+ <div className="space-y-2">
203
+ <label className="text-sm font-medium">
204
+ {formState.executionType === "bunx" ? "パッケージ名" : "コマンド"} *
205
+ </label>
206
+ <Input
207
+ type="text"
208
+ value={formState.command}
209
+ onChange={handleChange("command")}
210
+ />
211
+ {errors.command && (
212
+ <p className="text-xs text-destructive">{errors.command}</p>
213
+ )}
214
+ </div>
215
+ </div>
216
+
217
+ <div className="grid gap-4 sm:grid-cols-2">
218
+ <div className="space-y-2">
219
+ <label className="text-sm font-medium">
220
+ defaultArgs (改行区切り)
221
+ </label>
222
+ <textarea
223
+ value={formState.defaultArgs}
224
+ onChange={handleChange("defaultArgs")}
225
+ rows={2}
226
+ className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
227
+ />
228
+ </div>
229
+
230
+ <div className="space-y-2">
231
+ <label className="text-sm font-medium">
232
+ permissionSkipArgs (改行区切り)
233
+ </label>
234
+ <textarea
235
+ value={formState.permissionSkipArgs}
236
+ onChange={handleChange("permissionSkipArgs")}
237
+ rows={2}
238
+ className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
239
+ />
240
+ </div>
241
+ </div>
242
+
243
+ <div className="grid gap-4 sm:grid-cols-3">
244
+ <div className="space-y-2">
245
+ <label className="text-sm font-medium">normalモード引数</label>
246
+ <textarea
247
+ value={formState.modeNormal}
248
+ onChange={handleChange("modeNormal")}
249
+ rows={3}
250
+ className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
251
+ />
252
+ </div>
253
+ <div className="space-y-2">
254
+ <label className="text-sm font-medium">continueモード引数</label>
255
+ <textarea
256
+ value={formState.modeContinue}
257
+ onChange={handleChange("modeContinue")}
258
+ rows={3}
259
+ className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
260
+ />
261
+ </div>
262
+ <div className="space-y-2">
263
+ <label className="text-sm font-medium">resumeモード引数</label>
264
+ <textarea
265
+ value={formState.modeResume}
266
+ onChange={handleChange("modeResume")}
267
+ rows={3}
268
+ className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
269
+ />
270
+ </div>
271
+ </div>
272
+
273
+ <div className="space-y-2">
274
+ <label className="text-sm font-medium">
275
+ 環境変数 (key=value を改行で記述)
276
+ </label>
277
+ <textarea
278
+ value={formState.env}
279
+ onChange={handleChange("env")}
280
+ rows={3}
281
+ className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
282
+ />
283
+ {errors.env && <p className="text-xs text-destructive">{errors.env}</p>}
284
+ </div>
285
+ </form>
286
+ );
287
+ }
288
+
289
+ function createInitialState(initialValue?: CustomAITool) {
290
+ if (!initialValue) {
291
+ return {
292
+ id: "",
293
+ displayName: "",
294
+ icon: "",
295
+ description: "",
296
+ executionType: "bunx" as CustomAITool["executionType"],
297
+ command: "",
298
+ defaultArgs: "",
299
+ modeNormal: "",
300
+ modeContinue: "",
301
+ modeResume: "",
302
+ permissionSkipArgs: "",
303
+ env: "",
304
+ };
305
+ }
306
+
307
+ return {
308
+ id: initialValue.id,
309
+ displayName: initialValue.displayName,
310
+ icon: initialValue.icon ?? "",
311
+ description: initialValue.description ?? "",
312
+ executionType: initialValue.executionType,
313
+ command: initialValue.command,
314
+ defaultArgs: joinList(initialValue.defaultArgs),
315
+ modeNormal: joinList(initialValue.modeArgs?.normal),
316
+ modeContinue: joinList(initialValue.modeArgs?.continue),
317
+ modeResume: joinList(initialValue.modeArgs?.resume),
318
+ permissionSkipArgs: joinList(initialValue.permissionSkipArgs),
319
+ env: stringifyEnv(initialValue.env),
320
+ };
321
+ }
322
+
323
+ function joinList(values?: string[] | null): string {
324
+ if (!values || values.length === 0) {
325
+ return "";
326
+ }
327
+ return values.join("\n");
328
+ }
329
+
330
+ function parseList(value: string): string[] | null {
331
+ if (!value.trim()) {
332
+ return null;
333
+ }
334
+ const values = value
335
+ .split(/\n|,/)
336
+ .map((item) => item.trim())
337
+ .filter(Boolean);
338
+ return values.length ? values : null;
339
+ }
340
+
341
+ function stringifyEnv(env?: EnvironmentVariable[] | null): string {
342
+ if (!env || env.length === 0) {
343
+ return "";
344
+ }
345
+ return env
346
+ .filter((variable) => variable.key)
347
+ .map((variable) => `${variable.key}=${variable.value}`)
348
+ .join("\n");
349
+ }
350
+
351
+ function parseEnv(value: string): EnvironmentVariable[] | null | Error {
352
+ if (!value.trim()) {
353
+ return null;
354
+ }
355
+
356
+ const now = new Date().toISOString();
357
+ const result: EnvironmentVariable[] = [];
358
+ const seen = new Set<string>();
359
+ const lines = value
360
+ .split(/\n/)
361
+ .map((line) => line.trim())
362
+ .filter(Boolean);
363
+
364
+ for (const line of lines) {
365
+ const [rawKey, ...rest] = line.split("=");
366
+ const key = (rawKey ?? "").trim();
367
+ if (!key || rest.length === 0) {
368
+ return new Error("環境変数は key=value 形式で入力してください");
369
+ }
370
+ const val = rest.join("=").trim();
371
+ if (!val) {
372
+ return new Error(`${key} の値を入力してください`);
373
+ }
374
+ if (seen.has(key)) {
375
+ return new Error(`環境変数 ${key} が重複しています`);
376
+ }
377
+ seen.add(key);
378
+ result.push({
379
+ key,
380
+ value: val,
381
+ lastUpdated: now,
382
+ });
383
+ }
384
+
385
+ return result;
386
+ }