@akiojin/gwt 4.11.6 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/bin/gwt.js +1 -1
  2. package/dist/claude.d.ts +1 -0
  3. package/dist/claude.d.ts.map +1 -1
  4. package/dist/claude.js +50 -24
  5. package/dist/claude.js.map +1 -1
  6. package/dist/cli/ui/App.solid.d.ts.map +1 -1
  7. package/dist/cli/ui/App.solid.js +247 -49
  8. package/dist/cli/ui/App.solid.js.map +1 -1
  9. package/dist/cli/ui/components/solid/QuickStartStep.d.ts.map +1 -1
  10. package/dist/cli/ui/components/solid/QuickStartStep.js +35 -22
  11. package/dist/cli/ui/components/solid/QuickStartStep.js.map +1 -1
  12. package/dist/cli/ui/components/solid/SelectInput.d.ts.map +1 -1
  13. package/dist/cli/ui/components/solid/SelectInput.js +2 -1
  14. package/dist/cli/ui/components/solid/SelectInput.js.map +1 -1
  15. package/dist/cli/ui/components/solid/WizardController.d.ts.map +1 -1
  16. package/dist/cli/ui/components/solid/WizardController.js +19 -11
  17. package/dist/cli/ui/components/solid/WizardController.js.map +1 -1
  18. package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -1
  19. package/dist/cli/ui/components/solid/WizardSteps.js +26 -69
  20. package/dist/cli/ui/components/solid/WizardSteps.js.map +1 -1
  21. package/dist/cli/ui/core/theme.d.ts +9 -0
  22. package/dist/cli/ui/core/theme.d.ts.map +1 -1
  23. package/dist/cli/ui/core/theme.js +21 -0
  24. package/dist/cli/ui/core/theme.js.map +1 -1
  25. package/dist/cli/ui/screens/solid/BranchListScreen.d.ts +9 -2
  26. package/dist/cli/ui/screens/solid/BranchListScreen.d.ts.map +1 -1
  27. package/dist/cli/ui/screens/solid/BranchListScreen.js +101 -28
  28. package/dist/cli/ui/screens/solid/BranchListScreen.js.map +1 -1
  29. package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts +2 -1
  30. package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts.map +1 -1
  31. package/dist/cli/ui/screens/solid/ConfirmScreen.js +11 -3
  32. package/dist/cli/ui/screens/solid/ConfirmScreen.js.map +1 -1
  33. package/dist/cli/ui/screens/solid/EnvironmentScreen.d.ts.map +1 -1
  34. package/dist/cli/ui/screens/solid/EnvironmentScreen.js +9 -10
  35. package/dist/cli/ui/screens/solid/EnvironmentScreen.js.map +1 -1
  36. package/dist/cli/ui/screens/solid/LogScreen.d.ts +7 -1
  37. package/dist/cli/ui/screens/solid/LogScreen.d.ts.map +1 -1
  38. package/dist/cli/ui/screens/solid/LogScreen.js +254 -16
  39. package/dist/cli/ui/screens/solid/LogScreen.js.map +1 -1
  40. package/dist/cli/ui/screens/solid/ProfileEnvScreen.d.ts.map +1 -1
  41. package/dist/cli/ui/screens/solid/ProfileEnvScreen.js +8 -5
  42. package/dist/cli/ui/screens/solid/ProfileEnvScreen.js.map +1 -1
  43. package/dist/cli/ui/screens/solid/SelectorScreen.d.ts.map +1 -1
  44. package/dist/cli/ui/screens/solid/SelectorScreen.js +12 -4
  45. package/dist/cli/ui/screens/solid/SelectorScreen.js.map +1 -1
  46. package/dist/cli/ui/types.d.ts +1 -0
  47. package/dist/cli/ui/types.d.ts.map +1 -1
  48. package/dist/cli/ui/utils/branchFormatter.d.ts +1 -0
  49. package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
  50. package/dist/cli/ui/utils/branchFormatter.js +29 -7
  51. package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
  52. package/dist/cli/ui/utils/continueSession.d.ts +14 -0
  53. package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
  54. package/dist/cli/ui/utils/continueSession.js +61 -3
  55. package/dist/cli/ui/utils/continueSession.js.map +1 -1
  56. package/dist/cli/ui/utils/versionCache.d.ts +37 -0
  57. package/dist/cli/ui/utils/versionCache.d.ts.map +1 -0
  58. package/dist/cli/ui/utils/versionCache.js +70 -0
  59. package/dist/cli/ui/utils/versionCache.js.map +1 -0
  60. package/dist/cli/ui/utils/versionFetcher.d.ts +41 -0
  61. package/dist/cli/ui/utils/versionFetcher.d.ts.map +1 -0
  62. package/dist/cli/ui/utils/versionFetcher.js +89 -0
  63. package/dist/cli/ui/utils/versionFetcher.js.map +1 -0
  64. package/dist/codex.d.ts +1 -0
  65. package/dist/codex.d.ts.map +1 -1
  66. package/dist/codex.js +48 -19
  67. package/dist/codex.js.map +1 -1
  68. package/dist/config/index.d.ts.map +1 -1
  69. package/dist/config/index.js +10 -1
  70. package/dist/config/index.js.map +1 -1
  71. package/dist/gemini.d.ts +1 -0
  72. package/dist/gemini.d.ts.map +1 -1
  73. package/dist/gemini.js +36 -3
  74. package/dist/gemini.js.map +1 -1
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +32 -2
  77. package/dist/index.js.map +1 -1
  78. package/dist/launcher.d.ts.map +1 -1
  79. package/dist/launcher.js +43 -8
  80. package/dist/launcher.js.map +1 -1
  81. package/dist/logging/agentOutput.d.ts +21 -0
  82. package/dist/logging/agentOutput.d.ts.map +1 -0
  83. package/dist/logging/agentOutput.js +164 -0
  84. package/dist/logging/agentOutput.js.map +1 -0
  85. package/dist/logging/formatter.d.ts.map +1 -1
  86. package/dist/logging/formatter.js +18 -4
  87. package/dist/logging/formatter.js.map +1 -1
  88. package/dist/logging/logger.d.ts.map +1 -1
  89. package/dist/logging/logger.js +2 -0
  90. package/dist/logging/logger.js.map +1 -1
  91. package/dist/logging/reader.d.ts +21 -0
  92. package/dist/logging/reader.d.ts.map +1 -1
  93. package/dist/logging/reader.js +79 -0
  94. package/dist/logging/reader.js.map +1 -1
  95. package/dist/opentui/index.solid.js +2306 -653
  96. package/dist/services/dependency-installer.js +2 -2
  97. package/dist/services/dependency-installer.js.map +1 -1
  98. package/dist/utils/session/common.d.ts +8 -0
  99. package/dist/utils/session/common.d.ts.map +1 -1
  100. package/dist/utils/session/common.js +22 -0
  101. package/dist/utils/session/common.js.map +1 -1
  102. package/dist/utils/session/parsers/claude.d.ts +10 -4
  103. package/dist/utils/session/parsers/claude.d.ts.map +1 -1
  104. package/dist/utils/session/parsers/claude.js +64 -18
  105. package/dist/utils/session/parsers/claude.js.map +1 -1
  106. package/dist/utils/session/parsers/codex.d.ts.map +1 -1
  107. package/dist/utils/session/parsers/codex.js +48 -28
  108. package/dist/utils/session/parsers/codex.js.map +1 -1
  109. package/dist/utils/session/parsers/gemini.d.ts.map +1 -1
  110. package/dist/utils/session/parsers/gemini.js +43 -6
  111. package/dist/utils/session/parsers/gemini.js.map +1 -1
  112. package/dist/utils/session/parsers/opencode.d.ts.map +1 -1
  113. package/dist/utils/session/parsers/opencode.js +43 -6
  114. package/dist/utils/session/parsers/opencode.js.map +1 -1
  115. package/dist/utils/session/types.d.ts +7 -0
  116. package/dist/utils/session/types.d.ts.map +1 -1
  117. package/dist/web/client/src/components/ui/alert.d.ts +1 -1
  118. package/dist/worktree.d.ts +4 -1
  119. package/dist/worktree.d.ts.map +1 -1
  120. package/dist/worktree.js +21 -15
  121. package/dist/worktree.js.map +1 -1
  122. package/package.json +2 -1
  123. package/src/claude.ts +64 -28
  124. package/src/cli/ui/App.solid.tsx +324 -51
  125. package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +830 -1
  126. package/src/cli/ui/__tests__/solid/BranchListScreen.test.tsx +105 -5
  127. package/src/cli/ui/__tests__/solid/ConfirmScreen.test.tsx +77 -0
  128. package/src/cli/ui/__tests__/solid/LogScreen.test.tsx +351 -0
  129. package/src/cli/ui/__tests__/solid/components/QuickStartStep.test.tsx +73 -2
  130. package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +4 -1
  131. package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +72 -45
  132. package/src/cli/ui/components/solid/QuickStartStep.tsx +35 -23
  133. package/src/cli/ui/components/solid/SearchInput.tsx +1 -1
  134. package/src/cli/ui/components/solid/SelectInput.tsx +4 -0
  135. package/src/cli/ui/components/solid/WizardController.tsx +20 -11
  136. package/src/cli/ui/components/solid/WizardSteps.tsx +29 -86
  137. package/src/cli/ui/core/theme.ts +32 -0
  138. package/src/cli/ui/hooks/solid/useAsyncOperation.ts +8 -6
  139. package/src/cli/ui/hooks/solid/useGitOperations.ts +6 -5
  140. package/src/cli/ui/screens/solid/BranchListScreen.tsx +135 -32
  141. package/src/cli/ui/screens/solid/ConfirmScreen.tsx +20 -8
  142. package/src/cli/ui/screens/solid/EnvironmentScreen.tsx +22 -20
  143. package/src/cli/ui/screens/solid/LogScreen.tsx +364 -35
  144. package/src/cli/ui/screens/solid/ProfileEnvScreen.tsx +19 -15
  145. package/src/cli/ui/screens/solid/SelectorScreen.tsx +25 -14
  146. package/src/cli/ui/screens/solid/SettingsScreen.tsx +5 -3
  147. package/src/cli/ui/types.ts +1 -0
  148. package/src/cli/ui/utils/__tests__/branchFormatter.test.ts +53 -6
  149. package/src/cli/ui/utils/branchFormatter.ts +35 -7
  150. package/src/cli/ui/utils/continueSession.ts +90 -3
  151. package/src/cli/ui/utils/versionCache.ts +93 -0
  152. package/src/cli/ui/utils/versionFetcher.ts +120 -0
  153. package/src/codex.ts +62 -20
  154. package/src/config/__tests__/saveSession.test.ts +2 -2
  155. package/src/config/index.ts +11 -1
  156. package/src/gemini.ts +50 -4
  157. package/src/index.test.ts +16 -10
  158. package/src/index.ts +38 -1
  159. package/src/launcher.ts +49 -8
  160. package/src/logging/agentOutput.ts +216 -0
  161. package/src/logging/formatter.ts +23 -4
  162. package/src/logging/logger.ts +2 -0
  163. package/src/logging/reader.ts +117 -0
  164. package/src/services/__tests__/BatchMergeService.test.ts +34 -14
  165. package/src/services/dependency-installer.ts +2 -2
  166. package/src/utils/session/common.ts +28 -0
  167. package/src/utils/session/parsers/claude.ts +79 -29
  168. package/src/utils/session/parsers/codex.ts +50 -26
  169. package/src/utils/session/parsers/gemini.ts +46 -5
  170. package/src/utils/session/parsers/opencode.ts +46 -5
  171. package/src/utils/session/types.ts +4 -0
  172. package/src/worktree.ts +28 -15
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
1
2
  import { describe, it, expect } from "bun:test";
2
3
  import {
3
4
  formatBranchItem,
@@ -5,6 +6,31 @@ import {
5
6
  } from "../../utils/branchFormatter.js";
6
7
  import type { BranchInfo } from "../../types.js";
7
8
 
9
+ const LOCAL_DATE_TIME_FORMATTER = new Intl.DateTimeFormat(undefined, {
10
+ year: "numeric",
11
+ month: "2-digit",
12
+ day: "2-digit",
13
+ hour: "2-digit",
14
+ minute: "2-digit",
15
+ hour12: false,
16
+ });
17
+
18
+ const formatLocalDateTime = (timestampMs: number): string => {
19
+ const date = new Date(timestampMs);
20
+ const parts = LOCAL_DATE_TIME_FORMATTER.formatToParts(date);
21
+ const get = (type: Intl.DateTimeFormatPartTypes) =>
22
+ parts.find((part) => part.type === type)?.value;
23
+ const year = get("year");
24
+ const month = get("month");
25
+ const day = get("day");
26
+ const hour = get("hour");
27
+ const minute = get("minute");
28
+ if (!year || !month || !day || !hour || !minute) {
29
+ return LOCAL_DATE_TIME_FORMATTER.format(date);
30
+ }
31
+ return `${year}-${month}-${day} ${hour}:${minute}`;
32
+ };
33
+
8
34
  describe("branchFormatter", () => {
9
35
  describe("formatBranchItem", () => {
10
36
  it("should format a branch without icons", () => {
@@ -46,8 +72,9 @@ describe("branchFormatter", () => {
46
72
 
47
73
  const result = formatBranchItem(branchInfo);
48
74
 
49
- expect(result.lastToolUsageLabel).toContain("Codex");
50
- expect(result.lastToolUsageLabel).toContain("2025-11-26");
75
+ expect(result.lastToolUsageLabel).toBe(
76
+ `Codex@latest | ${formatLocalDateTime(branchInfo.lastToolUsage.timestamp)}`,
77
+ );
51
78
  });
52
79
 
53
80
  it("should set lastToolUsageLabel to null when no usage exists", () => {
@@ -156,13 +183,13 @@ describe("branchFormatter", () => {
156
183
 
157
184
  expect(results).toHaveLength(3);
158
185
  // Current branch (main) should be first
159
- expect(results[0].name).toBe("main");
160
- expect(results[0].isCurrent).toBe(true);
186
+ expect(results[0]!.name).toBe("main");
187
+ expect(results[0]!.isCurrent).toBe(true);
161
188
  // origin/main is also main branch, so it comes second
162
- expect(results[1].name).toBe("origin/main");
163
- expect(results[1].type).toBe("remote");
189
+ expect(results[1]!.name).toBe("origin/main");
190
+ expect(results[1]!.type).toBe("remote");
164
191
  // feature/test comes last
165
- expect(results[2].name).toBe("feature/test");
192
+ expect(results[2]!.name).toBe("feature/test");
166
193
  });
167
194
 
168
195
  it("should handle empty array", () => {
@@ -189,8 +216,8 @@ describe("branchFormatter", () => {
189
216
 
190
217
  const results = formatBranchItems(branches);
191
218
 
192
- expect(results[0].name).toBe("a-branch");
193
- expect(results[1].name).toBe("z-branch");
219
+ expect(results[0]!.name).toBe("a-branch");
220
+ expect(results[1]!.name).toBe("z-branch");
194
221
  });
195
222
  });
196
223
 
@@ -219,8 +246,8 @@ describe("branchFormatter", () => {
219
246
 
220
247
  const results = formatBranchItems(branches);
221
248
 
222
- expect(results[0].name).toBe("feature/current");
223
- expect(results[0].isCurrent).toBe(true);
249
+ expect(results[0]!.name).toBe("feature/current");
250
+ expect(results[0]!.isCurrent).toBe(true);
224
251
  });
225
252
 
226
253
  it("should prioritize main branch as second (after current)", () => {
@@ -247,8 +274,8 @@ describe("branchFormatter", () => {
247
274
 
248
275
  const results = formatBranchItems(branches);
249
276
 
250
- expect(results[0].name).toBe("main");
251
- expect(results[1].name).toBe("develop");
277
+ expect(results[0]!.name).toBe("main");
278
+ expect(results[1]!.name).toBe("develop");
252
279
  });
253
280
 
254
281
  it("should prioritize develop branch after main (when main exists)", () => {
@@ -275,9 +302,9 @@ describe("branchFormatter", () => {
275
302
 
276
303
  const results = formatBranchItems(branches);
277
304
 
278
- expect(results[0].name).toBe("main");
279
- expect(results[1].name).toBe("develop");
280
- expect(results[2].name).toBe("feature/test");
305
+ expect(results[0]!.name).toBe("main");
306
+ expect(results[1]!.name).toBe("develop");
307
+ expect(results[2]!.name).toBe("feature/test");
281
308
  });
282
309
 
283
310
  it("should NOT prioritize develop branch when main does not exist", () => {
@@ -305,9 +332,9 @@ describe("branchFormatter", () => {
305
332
  const results = formatBranchItems(branches);
306
333
 
307
334
  // develop should be sorted alphabetically, not prioritized
308
- expect(results[0].name).toBe("develop");
309
- expect(results[1].name).toBe("feature/a");
310
- expect(results[2].name).toBe("feature/z");
335
+ expect(results[0]!.name).toBe("develop");
336
+ expect(results[1]!.name).toBe("feature/a");
337
+ expect(results[2]!.name).toBe("feature/z");
311
338
  });
312
339
 
313
340
  it("should prioritize branches with worktree", () => {
@@ -346,8 +373,8 @@ describe("branchFormatter", () => {
346
373
 
347
374
  const results = formatBranchItems(branches, worktreeMap);
348
375
 
349
- expect(results[0].name).toBe("feature/with-worktree");
350
- expect(results[1].name).toBe("feature/no-worktree");
376
+ expect(results[0]!.name).toBe("feature/with-worktree");
377
+ expect(results[1]!.name).toBe("feature/no-worktree");
351
378
  });
352
379
 
353
380
  it("should sort branches with worktree by latest commit timestamp", () => {
@@ -403,8 +430,8 @@ describe("branchFormatter", () => {
403
430
 
404
431
  const results = formatBranchItems(branches, worktreeMap);
405
432
 
406
- expect(results[0].name).toBe("feature/recent");
407
- expect(results[1].name).toBe("feature/older");
433
+ expect(results[0]!.name).toBe("feature/recent");
434
+ expect(results[1]!.name).toBe("feature/older");
408
435
  });
409
436
 
410
437
  it("should prioritize local branches over remote branches", () => {
@@ -425,10 +452,10 @@ describe("branchFormatter", () => {
425
452
 
426
453
  const results = formatBranchItems(branches);
427
454
 
428
- expect(results[0].name).toBe("feature/local");
429
- expect(results[0].type).toBe("local");
430
- expect(results[1].name).toBe("origin/feature/remote");
431
- expect(results[1].type).toBe("remote");
455
+ expect(results[0]!.name).toBe("feature/local");
456
+ expect(results[0]!.type).toBe("local");
457
+ expect(results[1]!.name).toBe("origin/feature/remote");
458
+ expect(results[1]!.type).toBe("remote");
432
459
  });
433
460
 
434
461
  it("should sort by latest commit timestamp when worktree status matches", () => {
@@ -451,8 +478,8 @@ describe("branchFormatter", () => {
451
478
 
452
479
  const results = formatBranchItems(branches);
453
480
 
454
- expect(results[0].name).toBe("origin/feature/newer");
455
- expect(results[1].name).toBe("feature/local-older");
481
+ expect(results[0]!.name).toBe("origin/feature/newer");
482
+ expect(results[1]!.name).toBe("feature/local-older");
456
483
  });
457
484
 
458
485
  it("should apply all sorting rules in correct priority order", () => {
@@ -528,13 +555,13 @@ describe("branchFormatter", () => {
528
555
  // 4. Branches with worktree
529
556
  // 5. Local branches (alphabetically)
530
557
  // 6. Remote branches
531
- expect(results[0].name).toBe("feature/current");
532
- expect(results[1].name).toBe("main");
533
- expect(results[2].name).toBe("develop");
534
- expect(results[3].name).toBe("feature/with-worktree");
535
- expect(results[4].name).toBe("feature/a-local-no-worktree");
536
- expect(results[5].name).toBe("feature/z-local-no-worktree");
537
- expect(results[6].name).toBe("origin/feature/z-remote");
558
+ expect(results[0]!.name).toBe("feature/current");
559
+ expect(results[1]!.name).toBe("main");
560
+ expect(results[2]!.name).toBe("develop");
561
+ expect(results[3]!.name).toBe("feature/with-worktree");
562
+ expect(results[4]!.name).toBe("feature/a-local-no-worktree");
563
+ expect(results[5]!.name).toBe("feature/z-local-no-worktree");
564
+ expect(results[6]!.name).toBe("origin/feature/z-remote");
538
565
  });
539
566
 
540
567
  it("should handle release and hotfix branches without special priority", () => {
@@ -562,9 +589,9 @@ describe("branchFormatter", () => {
562
589
  const results = formatBranchItems(branches);
563
590
 
564
591
  // Should be sorted alphabetically (no special priority)
565
- expect(results[0].name).toBe("feature/test");
566
- expect(results[1].name).toBe("hotfix/urgent");
567
- expect(results[2].name).toBe("release/v1.0");
592
+ expect(results[0]!.name).toBe("feature/test");
593
+ expect(results[1]!.name).toBe("hotfix/urgent");
594
+ expect(results[2]!.name).toBe("release/v1.0");
568
595
  });
569
596
 
570
597
  it("should sort by latest activity time (max of git commit and tool usage)", () => {
@@ -604,8 +631,8 @@ describe("branchFormatter", () => {
604
631
  // Both have same latest activity time (1_800_000_000), so alphabetical
605
632
  // feature/git-newer: max(1_800_000_000, 1_700_000_000) = 1_800_000_000
606
633
  // feature/tool-newer: max(1_700_000_000, 1_800_000_000) = 1_800_000_000
607
- expect(results[0].name).toBe("feature/git-newer");
608
- expect(results[1].name).toBe("feature/tool-newer");
634
+ expect(results[0]!.name).toBe("feature/git-newer");
635
+ expect(results[1]!.name).toBe("feature/tool-newer");
609
636
  });
610
637
 
611
638
  it("should prioritize branch with tool usage over branch with only git commit when tool is newer", () => {
@@ -636,8 +663,8 @@ describe("branchFormatter", () => {
636
663
  const results = formatBranchItems(branches);
637
664
 
638
665
  // feature/with-tool has newer activity (tool usage at 1_800_000_000)
639
- expect(results[0].name).toBe("feature/with-tool");
640
- expect(results[1].name).toBe("feature/git-only");
666
+ expect(results[0]!.name).toBe("feature/with-tool");
667
+ expect(results[1]!.name).toBe("feature/git-only");
641
668
  });
642
669
 
643
670
  it("should prioritize branch with newer git commit over branch with older tool usage", () => {
@@ -668,8 +695,8 @@ describe("branchFormatter", () => {
668
695
  const results = formatBranchItems(branches);
669
696
 
670
697
  // feature/new-git has newer activity (git commit at 1_800_000_000)
671
- expect(results[0].name).toBe("feature/new-git");
672
- expect(results[1].name).toBe("feature/old-tool");
698
+ expect(results[0]!.name).toBe("feature/new-git");
699
+ expect(results[1]!.name).toBe("feature/old-tool");
673
700
  });
674
701
  });
675
702
  });
@@ -42,40 +42,52 @@ export function QuickStartStep(props: QuickStartStepProps) {
42
42
  }
43
43
  });
44
44
 
45
- // Build selection items from history
46
- // 最新の履歴エントリのみを使用(重複排除済み)
47
- const latestEntry = createMemo(() => props.history[0] ?? null);
45
+ const buildSettingsDescription = (entry: ToolSessionEntry): string => {
46
+ const parts: string[] = [];
47
+ if (entry.toolLabel) {
48
+ parts.push(entry.toolLabel);
49
+ } else if (entry.toolId) {
50
+ parts.push(entry.toolId);
51
+ }
52
+ if (entry.model) {
53
+ parts.push(entry.model);
54
+ }
55
+ if (entry.toolId === "codex-cli" && entry.reasoningLevel) {
56
+ parts.push(entry.reasoningLevel);
57
+ }
58
+ return parts.join(", ");
59
+ };
48
60
 
49
61
  const items = createMemo<QuickStartItem[]>(() => {
50
62
  const result: QuickStartItem[] = [];
51
- const entry = latestEntry();
52
-
53
- if (entry) {
54
- const reasoningInfo = entry.reasoningLevel
55
- ? `, ${entry.reasoningLevel}`
56
- : "";
57
- const versionInfo = entry.toolVersion ? `@${entry.toolVersion}` : "";
58
- const settingsDesc = `${entry.toolLabel}${versionInfo}, ${entry.model}${reasoningInfo}`;
59
- const resumeDesc = entry.sessionId
60
- ? `${settingsDesc}, session ${entry.sessionId}`
61
- : settingsDesc;
62
63
 
63
- result.push({
64
- label: "Resume session (previous settings)",
65
- value: `resume-${entry.toolId}`,
66
- description: resumeDesc,
67
- action: "resume",
68
- entry,
69
- });
64
+ props.history.forEach((entry, index) => {
65
+ if (!entry) return;
66
+ const settingsDesc = buildSettingsDescription(entry);
67
+ const sessionId =
68
+ entry.sessionId && entry.sessionId.trim().length > 0
69
+ ? entry.sessionId.trim()
70
+ : null;
71
+ const suffix = `${entry.toolId ?? "tool"}-${index}`;
72
+
73
+ if (sessionId) {
74
+ result.push({
75
+ label: "Resume session (previous settings)",
76
+ value: `resume-${suffix}`,
77
+ description: `${settingsDesc} | Session: ${sessionId}`,
78
+ action: "resume",
79
+ entry,
80
+ });
81
+ }
70
82
 
71
83
  result.push({
72
84
  label: "Start new (previous settings)",
73
- value: `start-new-${entry.toolId}`,
85
+ value: `start-new-${suffix}`,
74
86
  description: settingsDesc,
75
87
  action: "start-new",
76
88
  entry,
77
89
  });
78
- }
90
+ });
79
91
 
80
92
  // Add "Choose different settings..." at the end
81
93
  result.push({
@@ -27,7 +27,7 @@ export function SearchInput({
27
27
  <input
28
28
  value={value}
29
29
  onChange={onChange}
30
- onSubmit={onSubmit}
30
+ {...(onSubmit ? { onSubmit } : {})}
31
31
  placeholder={placeholder}
32
32
  width={inputWidth}
33
33
  focused={isFocused}
@@ -1,6 +1,7 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
  import type { SelectOption, SelectRenderable } from "@opentui/core";
3
3
  import type { Ref } from "solid-js";
4
+ import { selectionStyle } from "../../core/theme.js";
4
5
 
5
6
  export interface SelectInputItem {
6
7
  label: string;
@@ -69,6 +70,9 @@ export function SelectInput(props: SelectInputProps) {
69
70
  <select
70
71
  options={options()}
71
72
  height={computedHeight()}
73
+ selectedBackgroundColor={selectionStyle.bg}
74
+ selectedTextColor={selectionStyle.fg}
75
+ selectedDescriptionColor={selectionStyle.fg}
72
76
  {...(props.selectedIndex !== undefined && {
73
77
  selectedIndex: props.selectedIndex,
74
78
  })}
@@ -88,8 +88,8 @@ export function WizardController(props: WizardControllerProps) {
88
88
  const [executionMode, setExecutionMode] =
89
89
  createSignal<ExecutionMode>("normal");
90
90
 
91
- // キー伝播防止: 最初のフレームでは focused を無効にする
92
- const [isInitialFrame, setIsInitialFrame] = createSignal(true);
91
+ // キー伝播防止: ステップ遷移直後は focused を無効にする
92
+ const [isTransitioning, setIsTransitioning] = createSignal(true);
93
93
 
94
94
  // Reset state when wizard becomes visible
95
95
  function getInitialStep(): WizardStep {
@@ -115,6 +115,13 @@ export function WizardController(props: WizardControllerProps) {
115
115
  setExecutionMode("normal");
116
116
  };
117
117
 
118
+ // ステップ遷移時にキー伝搬防止を有効化するヘルパー
119
+ const startTransition = () => {
120
+ setIsTransitioning(true);
121
+ // 50ms後にフォーカスを有効化(キー伝搬を防止しつつ、応答性を維持)
122
+ setTimeout(() => setIsTransitioning(false), 50);
123
+ };
124
+
118
125
  // Reset wizard when it becomes visible
119
126
  let prevVisible = false;
120
127
  createEffect(() => {
@@ -123,19 +130,17 @@ export function WizardController(props: WizardControllerProps) {
123
130
  resetWizard();
124
131
  // キー伝播防止: 最初の数フレームでは focused を無効にする
125
132
  // Enter キーが複数フレームにわたって伝播する可能性があるため、長めに設定
126
- setIsInitialFrame(true);
127
- // 十分な時間を置いて focused を有効化 (150ms)
128
- setTimeout(() => setIsInitialFrame(false), 150);
133
+ startTransition();
129
134
  }
130
135
  prevVisible = visible;
131
136
  });
132
137
 
133
138
  // Handle keyboard events for step navigation
134
- // T412: 最初のフレームでは Enter キーをブロックして伝播を防ぐ
139
+ // T412: ステップ遷移直後は Enter キーをブロックして伝播を防ぐ
135
140
  useKeyboard((key) => {
136
141
  if (!props.visible) return;
137
- // 最初のフレームでは Enter キーを無視(ブランチ選択からの伝播防止)
138
- if (isInitialFrame() && key.name === "return") {
142
+ // ステップ遷移直後は Enter キーを無視(ステップ間のキー伝播防止)
143
+ if (isTransitioning() && key.name === "return") {
139
144
  return;
140
145
  }
141
146
  if (key.name === "escape") {
@@ -146,6 +151,8 @@ export function WizardController(props: WizardControllerProps) {
146
151
  const goToStep = (nextStep: WizardStep) => {
147
152
  setStepHistory((prev) => [...prev, step()]);
148
153
  setStep(nextStep);
154
+ // ステップ遷移時にキー伝搬防止を有効化
155
+ startTransition();
149
156
  };
150
157
 
151
158
  const goBack = () => {
@@ -157,6 +164,8 @@ export function WizardController(props: WizardControllerProps) {
157
164
  const previousStep = history[history.length - 1] ?? "agent-select";
158
165
  setStepHistory(history.slice(0, -1));
159
166
  setStep(previousStep);
167
+ // ステップ遷移時にキー伝搬防止を有効化
168
+ startTransition();
160
169
  };
161
170
 
162
171
  // Determine if reasoning level step is needed
@@ -204,8 +213,8 @@ export function WizardController(props: WizardControllerProps) {
204
213
  };
205
214
 
206
215
  const handleVersionSelect = (version: string) => {
207
- // "installed" null として保存(bunx のデフォルト動作)
208
- setSelectedVersion(version === "installed" ? null : version);
216
+ // "installed" を明示的に保存し、未指定時は後方互換で "latest" にフォールバックできるようにする
217
+ setSelectedVersion(version);
209
218
  goToStep("model-select");
210
219
  };
211
220
 
@@ -258,7 +267,7 @@ export function WizardController(props: WizardControllerProps) {
258
267
 
259
268
  const renderStep = () => {
260
269
  const currentStep = step();
261
- const focused = !isInitialFrame();
270
+ const focused = !isTransitioning();
262
271
 
263
272
  if (currentStep === "action-select") {
264
273
  return (
@@ -9,12 +9,13 @@ import { getModelOptions } from "../../utils/modelOptions.js";
9
9
  import type { CodingAgentId } from "../../types.js";
10
10
  import { useWizardScroll } from "./WizardPopup.js";
11
11
  import { getAgentTerminalColor } from "../../../../utils/coding-agent-colors.js";
12
+ import { getVersionCache } from "../../utils/versionCache.js";
13
+ import { selectionStyle } from "../../core/theme.js";
12
14
  import {
13
- fetchPackageVersions,
14
- parsePackageCommand,
15
- } from "../../../../utils/npmRegistry.js";
16
- import { BUILTIN_CODING_AGENTS } from "../../../../config/builtin-coding-agents.js";
17
- import { findCommand } from "../../../../utils/command.js";
15
+ fetchInstalledVersionForAgent,
16
+ versionInfoToSelectItem,
17
+ createInstalledOption,
18
+ } from "../../utils/versionFetcher.js";
18
19
 
19
20
  /**
20
21
  * WizardSteps - ウィザードの各ステップコンポーネント
@@ -346,7 +347,7 @@ export function AgentSelectStep(props: AgentSelectStepProps) {
346
347
  const isSelected = () => selectedIndex() === index;
347
348
  const agentColor = getAgentTerminalColor(agent.value);
348
349
  return isSelected() ? (
349
- <text bg="cyan" fg="black">
350
+ <text bg={selectionStyle.bg} fg={selectionStyle.fg}>
350
351
  {"> "}
351
352
  {agent.label}
352
353
  </text>
@@ -626,89 +627,31 @@ const LATEST_OPTION: SelectInputItem = {
626
627
  description: "Always fetch latest version",
627
628
  };
628
629
 
629
- // エージェントIDからパッケージ名を取得
630
- function getPackageNameForAgent(agentId: string): string | null {
631
- const agent = BUILTIN_CODING_AGENTS.find((a) => a.id === agentId);
632
- if (!agent || agent.type !== "bunx") {
633
- return null;
634
- }
635
- const { packageName } = parsePackageCommand(agent.command);
636
- return packageName;
637
- }
638
-
639
- // バージョン一覧を取得
640
- async function fetchVersionOptions(
641
- agentId: string,
642
- ): Promise<SelectInputItem[]> {
643
- const packageName = getPackageNameForAgent(agentId);
644
- if (!packageName) {
645
- return [];
646
- }
647
-
648
- const versions = await fetchPackageVersions(packageName);
649
- return versions.map((v) => {
650
- const item: SelectInputItem = {
651
- label: v.isPrerelease ? `${v.version} (pre)` : v.version,
652
- value: v.version,
653
- };
654
- if (v.publishedAt) {
655
- item.description = new Date(v.publishedAt).toLocaleDateString();
656
- }
657
- return item;
658
- });
659
- }
660
-
661
- // エージェントIDからコマンド名へのマッピング
662
- const AGENT_COMMAND_MAP: Record<string, string> = {
663
- "claude-code": "claude",
664
- "codex-cli": "codex",
665
- "gemini-cli": "gemini",
666
- opencode: "opencode",
667
- };
668
-
669
- // インストール済みコマンド情報を取得(findCommandと統一)
670
- async function fetchInstalledOption(
671
- agentId: string,
672
- ): Promise<SelectInputItem | null> {
673
- const commandName = AGENT_COMMAND_MAP[agentId];
674
- if (!commandName) {
675
- return null;
676
- }
677
-
678
- const result = await findCommand(commandName);
679
- if (result.source !== "installed" || !result.path) {
680
- return null;
681
- }
682
-
683
- // バージョン表示(v1.0.3 → 1.0.3 形式に)
684
- const version = result.version?.replace(/^v/, "") ?? "unknown";
685
-
686
- return {
687
- label: `installed@${version}`,
688
- value: "installed",
689
- description: result.path,
690
- };
691
- }
692
-
693
630
  export function VersionSelectStep(props: VersionSelectStepProps) {
694
631
  const [selectedIndex, setSelectedIndex] = createSignal(0);
695
632
  const [selectRef, setSelectRef] = createSignal<SelectRenderable | undefined>(
696
633
  undefined,
697
634
  );
698
635
 
699
- // npmレジストリからバージョン取得
700
- const [versionOptions] = createResource(
701
- () => props.agentId,
702
- fetchVersionOptions,
703
- );
636
+ // FR-029: Use cached versions from startup prefetch (no npm registry re-access)
637
+ const cachedVersions = () => {
638
+ const cached = getVersionCache(props.agentId);
639
+ if (!cached) {
640
+ return []; // FR-031: Fallback to empty, UI will show "latest" only
641
+ }
642
+ return cached.map(versionInfoToSelectItem);
643
+ };
704
644
 
705
- // インストール済み情報を取得
645
+ // インストール済み情報を取得 (still needs async fetch for local command check)
706
646
  const [installedOption] = createResource(
707
647
  () => props.agentId,
708
- fetchInstalledOption,
648
+ async (agentId: string) => {
649
+ const installed = await fetchInstalledVersionForAgent(agentId);
650
+ return installed ? createInstalledOption(installed) : null;
651
+ },
709
652
  );
710
653
 
711
- // 全オプション(installed + latest + 動的)
654
+ // 全オプション(installed + latest + cached versions)
712
655
  const allOptions = (): SelectInputItem[] => {
713
656
  const options: SelectInputItem[] = [];
714
657
 
@@ -721,9 +664,9 @@ export function VersionSelectStep(props: VersionSelectStepProps) {
721
664
  // latestオプション(常に表示)
722
665
  options.push(LATEST_OPTION);
723
666
 
724
- // npmレジストリから取得したバージョン
725
- const dynamic = versionOptions() ?? [];
726
- options.push(...dynamic);
667
+ // FR-029: Use cached versions (no npm registry re-access)
668
+ const cached = cachedVersions();
669
+ options.push(...cached);
727
670
 
728
671
  return options;
729
672
  };
@@ -761,11 +704,11 @@ export function VersionSelectStep(props: VersionSelectStepProps) {
761
704
  };
762
705
 
763
706
  const statusText = () => {
764
- if (versionOptions.loading) {
765
- return "Fetching versions...";
766
- }
767
- if (versionOptions.error) {
768
- return "Could not fetch versions";
707
+ // FR-029: No loading state since we use cached versions from startup
708
+ // FR-031: Show message if cache is empty (failed to fetch at startup)
709
+ const cached = cachedVersions();
710
+ if (cached.length === 0) {
711
+ return "Version list unavailable (using latest)";
769
712
  }
770
713
  return null;
771
714
  };
@@ -33,6 +33,8 @@ export const colors = {
33
33
  selected: "cyan",
34
34
  highlighted: "cyan",
35
35
  disabled: "gray",
36
+ selectionBackground: "cyan",
37
+ selectionText: "black",
36
38
 
37
39
  // Branch type colors
38
40
  branchFeature: "green",
@@ -64,6 +66,36 @@ export const colors = {
64
66
  export type ColorName = keyof typeof colors;
65
67
  export type ColorValue = (typeof colors)[ColorName];
66
68
 
69
+ export type LogLevelLabel =
70
+ | "TRACE"
71
+ | "DEBUG"
72
+ | "INFO"
73
+ | "WARN"
74
+ | "ERROR"
75
+ | "FATAL";
76
+
77
+ export const logLevelColors: Record<LogLevelLabel, ColorValue> = {
78
+ TRACE: colors.textDim,
79
+ DEBUG: colors.info,
80
+ INFO: colors.success,
81
+ WARN: colors.warning,
82
+ ERROR: colors.error,
83
+ FATAL: colors.error,
84
+ };
85
+
86
+ export const selectionStyle = {
87
+ fg: colors.selectionText,
88
+ bg: colors.selectionBackground,
89
+ } as const;
90
+
91
+ export const getLogLevelColor = (label?: string | null): ColorValue => {
92
+ if (!label) {
93
+ return colors.text;
94
+ }
95
+ const normalized = label.toUpperCase() as LogLevelLabel;
96
+ return logLevelColors[normalized] ?? colors.text;
97
+ };
98
+
67
99
  // ========================================
68
100
  // Icon Definitions
69
101
  // ========================================
@@ -32,12 +32,14 @@ export function useAsyncOperation<T, Args extends unknown[]>(
32
32
  );
33
33
 
34
34
  const isLoading = createMemo(() => state().status === "loading");
35
- const error = createMemo(() =>
36
- state().status === "error" ? state().error : null,
37
- );
38
- const data = createMemo(() =>
39
- state().status === "success" ? state().data : null,
40
- );
35
+ const error = createMemo(() => {
36
+ const current = state();
37
+ return current.status === "error" ? current.error : null;
38
+ });
39
+ const data = createMemo(() => {
40
+ const current = state();
41
+ return current.status === "success" ? current.data : null;
42
+ });
41
43
 
42
44
  const setState = (next: AsyncState<T>) => {
43
45
  setStateInternal(next);