@akiojin/gwt 4.7.0 → 4.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/dist/claude.js +1 -1
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.js +1 -1
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +5 -6
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/LogDatePickerScreen.js +1 -1
- package/dist/cli/ui/components/screens/LogDatePickerScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/LogDetailScreen.js +1 -1
- package/dist/cli/ui/components/screens/LogDetailScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/LogListScreen.js +1 -1
- package/dist/cli/ui/components/screens/LogListScreen.js.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts +5 -0
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +18 -5
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +0 -1
- package/dist/codex.js.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +1 -2
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +106 -90
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/claude.ts +1 -1
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +1 -1
- package/src/cli/ui/__tests__/components/App.test.tsx +65 -3
- package/src/cli/ui/__tests__/components/screens/LogDetailScreen.test.tsx +1 -1
- package/src/cli/ui/__tests__/components/screens/LogListScreen.test.tsx +1 -1
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +83 -22
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +57 -37
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +105 -0
- package/src/cli/ui/components/App.tsx +1 -1
- package/src/cli/ui/components/screens/BranchListScreen.tsx +5 -5
- package/src/cli/ui/components/screens/LogDatePickerScreen.tsx +1 -1
- package/src/cli/ui/components/screens/LogDetailScreen.tsx +1 -1
- package/src/cli/ui/components/screens/LogListScreen.tsx +1 -1
- package/src/cli/ui/utils/branchFormatter.ts +19 -5
- package/src/codex.ts +0 -1
- package/src/gemini.ts +1 -2
- package/src/index.ts +148 -133
package/src/index.ts
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
hasUnpushedCommits,
|
|
14
14
|
getUncommittedChangesCount,
|
|
15
15
|
getUnpushedCommitsCount,
|
|
16
|
-
pushBranchToRemote,
|
|
17
16
|
GitError,
|
|
18
17
|
} from "./git.js";
|
|
19
18
|
import { launchClaudeCode } from "./claude.js";
|
|
@@ -57,7 +56,7 @@ import {
|
|
|
57
56
|
DependencyInstallError,
|
|
58
57
|
type DependencyInstallResult,
|
|
59
58
|
} from "./services/dependency-installer.js";
|
|
60
|
-
import {
|
|
59
|
+
import { waitForEnter } from "./utils/prompt.js";
|
|
61
60
|
|
|
62
61
|
const ERROR_PROMPT = chalk.yellow(
|
|
63
62
|
"Review the error details, then press Enter to continue.",
|
|
@@ -435,7 +434,7 @@ export async function handleAIToolWorkflow(
|
|
|
435
434
|
switch (dependencyStatus.reason) {
|
|
436
435
|
case "missing-lockfile":
|
|
437
436
|
warningMessage =
|
|
438
|
-
"Skipping automatic install because no lockfiles (bun.lock / pnpm-lock.yaml / package-lock.json) or package.json
|
|
437
|
+
"Skipping automatic install because no lockfiles (bun.lock / pnpm-lock.yaml / package-lock.json) or package.json could be found. Run the appropriate package-manager install command manually if needed.";
|
|
439
438
|
break;
|
|
440
439
|
case "missing-binary":
|
|
441
440
|
warningMessage = `Package manager '${dependencyStatus.manager ?? "unknown"}' is not available in this environment; skipping automatic install.`;
|
|
@@ -554,23 +553,21 @@ export async function handleAIToolWorkflow(
|
|
|
554
553
|
throw new Error(`Tool not found: ${tool}`);
|
|
555
554
|
}
|
|
556
555
|
|
|
557
|
-
// Save selection immediately so "last tool" is reflected
|
|
558
|
-
// is interrupted or killed mid-run (e.g., Ctrl+C).
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
{ skipHistory: true },
|
|
573
|
-
);
|
|
556
|
+
// Save selection immediately (including history) so "last tool" is reflected
|
|
557
|
+
// even if the tool is interrupted or killed mid-run (e.g., Ctrl+C).
|
|
558
|
+
// FR-042: Record timestamp to session history immediately on tool start.
|
|
559
|
+
await saveSession({
|
|
560
|
+
lastWorktreePath: worktreePath,
|
|
561
|
+
lastBranch: branch,
|
|
562
|
+
lastUsedTool: tool,
|
|
563
|
+
toolLabel: toolConfig.displayName ?? tool,
|
|
564
|
+
mode,
|
|
565
|
+
model: normalizedModel ?? null,
|
|
566
|
+
reasoningLevel: inferenceLevel ?? null,
|
|
567
|
+
skipPermissions: skipPermissions ?? null,
|
|
568
|
+
timestamp: Date.now(),
|
|
569
|
+
repositoryRoot: repoRoot,
|
|
570
|
+
});
|
|
574
571
|
|
|
575
572
|
// Lookup saved session ID for Continue (auto attach)
|
|
576
573
|
let resumeSessionId: string | null =
|
|
@@ -600,96 +597,127 @@ export async function handleAIToolWorkflow(
|
|
|
600
597
|
|
|
601
598
|
const launchStartedAt = Date.now();
|
|
602
599
|
|
|
600
|
+
// FR-043: Start periodic timestamp update timer (30 seconds interval)
|
|
601
|
+
// This ensures the latest activity time is updated even if the tool is force-killed
|
|
602
|
+
const SESSION_UPDATE_INTERVAL_MS = 30_000;
|
|
603
|
+
const updateTimer = setInterval(async () => {
|
|
604
|
+
try {
|
|
605
|
+
await saveSession(
|
|
606
|
+
{
|
|
607
|
+
lastWorktreePath: worktreePath,
|
|
608
|
+
lastBranch: branch,
|
|
609
|
+
lastUsedTool: tool,
|
|
610
|
+
toolLabel: toolConfig.displayName ?? tool,
|
|
611
|
+
mode,
|
|
612
|
+
model: normalizedModel ?? null,
|
|
613
|
+
reasoningLevel: inferenceLevel ?? null,
|
|
614
|
+
skipPermissions: skipPermissions ?? null,
|
|
615
|
+
timestamp: Date.now(),
|
|
616
|
+
repositoryRoot: repoRoot,
|
|
617
|
+
},
|
|
618
|
+
{ skipHistory: true }, // Don't add to history, just update timestamp
|
|
619
|
+
);
|
|
620
|
+
} catch {
|
|
621
|
+
// Ignore errors during periodic update
|
|
622
|
+
}
|
|
623
|
+
}, SESSION_UPDATE_INTERVAL_MS);
|
|
624
|
+
|
|
603
625
|
// Launch selected AI tool
|
|
604
626
|
// Builtin tools use their dedicated launch functions
|
|
605
627
|
// Custom tools use the generic launchCustomAITool function
|
|
606
628
|
let launchResult: { sessionId?: string | null } | void;
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
mode
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
mode
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
? "
|
|
670
|
-
: "
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
629
|
+
try {
|
|
630
|
+
if (tool === "claude-code") {
|
|
631
|
+
const launchOptions: {
|
|
632
|
+
mode?: "normal" | "continue" | "resume";
|
|
633
|
+
skipPermissions?: boolean;
|
|
634
|
+
envOverrides?: Record<string, string>;
|
|
635
|
+
model?: string;
|
|
636
|
+
sessionId?: string | null;
|
|
637
|
+
chrome?: boolean;
|
|
638
|
+
} = {
|
|
639
|
+
mode:
|
|
640
|
+
mode === "resume"
|
|
641
|
+
? "resume"
|
|
642
|
+
: mode === "continue"
|
|
643
|
+
? "continue"
|
|
644
|
+
: "normal",
|
|
645
|
+
skipPermissions,
|
|
646
|
+
envOverrides: sharedEnv,
|
|
647
|
+
sessionId: resumeSessionId,
|
|
648
|
+
chrome: true,
|
|
649
|
+
};
|
|
650
|
+
if (normalizedModel) {
|
|
651
|
+
launchOptions.model = normalizedModel;
|
|
652
|
+
}
|
|
653
|
+
launchResult = await launchClaudeCode(worktreePath, launchOptions);
|
|
654
|
+
} else if (tool === "codex-cli") {
|
|
655
|
+
const launchOptions: {
|
|
656
|
+
mode?: "normal" | "continue" | "resume";
|
|
657
|
+
bypassApprovals?: boolean;
|
|
658
|
+
envOverrides?: Record<string, string>;
|
|
659
|
+
model?: string;
|
|
660
|
+
reasoningEffort?: CodexReasoningEffort;
|
|
661
|
+
sessionId?: string | null;
|
|
662
|
+
} = {
|
|
663
|
+
mode:
|
|
664
|
+
mode === "resume"
|
|
665
|
+
? "resume"
|
|
666
|
+
: mode === "continue"
|
|
667
|
+
? "continue"
|
|
668
|
+
: "normal",
|
|
669
|
+
bypassApprovals: skipPermissions,
|
|
670
|
+
envOverrides: sharedEnv,
|
|
671
|
+
sessionId: resumeSessionId,
|
|
672
|
+
};
|
|
673
|
+
if (normalizedModel) {
|
|
674
|
+
launchOptions.model = normalizedModel;
|
|
675
|
+
}
|
|
676
|
+
if (inferenceLevel) {
|
|
677
|
+
launchOptions.reasoningEffort =
|
|
678
|
+
inferenceLevel as CodexReasoningEffort;
|
|
679
|
+
}
|
|
680
|
+
launchResult = await launchCodexCLI(worktreePath, launchOptions);
|
|
681
|
+
} else if (tool === "gemini-cli") {
|
|
682
|
+
const launchOptions: {
|
|
683
|
+
mode?: "normal" | "continue" | "resume";
|
|
684
|
+
skipPermissions?: boolean;
|
|
685
|
+
envOverrides?: Record<string, string>;
|
|
686
|
+
model?: string;
|
|
687
|
+
sessionId?: string | null;
|
|
688
|
+
} = {
|
|
689
|
+
mode:
|
|
690
|
+
mode === "resume"
|
|
691
|
+
? "resume"
|
|
692
|
+
: mode === "continue"
|
|
693
|
+
? "continue"
|
|
694
|
+
: "normal",
|
|
695
|
+
skipPermissions,
|
|
696
|
+
envOverrides: sharedEnv,
|
|
697
|
+
sessionId: resumeSessionId,
|
|
698
|
+
};
|
|
699
|
+
if (normalizedModel) {
|
|
700
|
+
launchOptions.model = normalizedModel;
|
|
701
|
+
}
|
|
702
|
+
launchResult = await launchGeminiCLI(worktreePath, launchOptions);
|
|
703
|
+
} else {
|
|
704
|
+
// Custom tool
|
|
705
|
+
printInfo(`Launching custom tool: ${toolConfig.displayName}`);
|
|
706
|
+
launchResult = await launchCustomAITool(toolConfig, {
|
|
707
|
+
mode:
|
|
708
|
+
mode === "resume"
|
|
709
|
+
? "resume"
|
|
710
|
+
: mode === "continue"
|
|
711
|
+
? "continue"
|
|
712
|
+
: "normal",
|
|
713
|
+
skipPermissions,
|
|
714
|
+
cwd: worktreePath,
|
|
715
|
+
sharedEnv,
|
|
716
|
+
});
|
|
677
717
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
printInfo(`Launching custom tool: ${toolConfig.displayName}`);
|
|
682
|
-
launchResult = await launchCustomAITool(toolConfig, {
|
|
683
|
-
mode:
|
|
684
|
-
mode === "resume"
|
|
685
|
-
? "resume"
|
|
686
|
-
: mode === "continue"
|
|
687
|
-
? "continue"
|
|
688
|
-
: "normal",
|
|
689
|
-
skipPermissions,
|
|
690
|
-
cwd: worktreePath,
|
|
691
|
-
sharedEnv,
|
|
692
|
-
});
|
|
718
|
+
} finally {
|
|
719
|
+
// FR-043: Clear the periodic timestamp update timer
|
|
720
|
+
clearInterval(updateTimer);
|
|
693
721
|
}
|
|
694
722
|
|
|
695
723
|
// Persist session with captured session ID (if any)
|
|
@@ -768,19 +796,21 @@ export async function handleAIToolWorkflow(
|
|
|
768
796
|
lastSessionId: finalSessionId,
|
|
769
797
|
});
|
|
770
798
|
|
|
771
|
-
let uncommittedExists = false;
|
|
772
799
|
try {
|
|
773
800
|
const [hasUncommitted, hasUnpushed] = await Promise.all([
|
|
774
801
|
hasUncommittedChanges(worktreePath),
|
|
775
802
|
hasUnpushedCommits(worktreePath, branch),
|
|
776
803
|
]);
|
|
777
|
-
uncommittedExists = hasUncommitted;
|
|
778
804
|
|
|
779
805
|
if (hasUncommitted) {
|
|
780
806
|
const uncommittedCount = await getUncommittedChangesCount(worktreePath);
|
|
781
807
|
const countLabel =
|
|
782
|
-
uncommittedCount > 0
|
|
783
|
-
|
|
808
|
+
uncommittedCount > 0
|
|
809
|
+
? ` (${uncommittedCount} ${
|
|
810
|
+
uncommittedCount === 1 ? "change" : "changes"
|
|
811
|
+
})`
|
|
812
|
+
: "";
|
|
813
|
+
printWarning(`Uncommitted changes detected${countLabel}.`);
|
|
784
814
|
}
|
|
785
815
|
|
|
786
816
|
if (hasUnpushed) {
|
|
@@ -788,36 +818,21 @@ export async function handleAIToolWorkflow(
|
|
|
788
818
|
worktreePath,
|
|
789
819
|
branch,
|
|
790
820
|
);
|
|
791
|
-
const countLabel =
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
try {
|
|
799
|
-
await pushBranchToRemote(worktreePath, branch);
|
|
800
|
-
printInfo(`Push completed for ${branch}.`);
|
|
801
|
-
} catch (error) {
|
|
802
|
-
const details =
|
|
803
|
-
error instanceof Error ? error.message : String(error);
|
|
804
|
-
printWarning(`Push failed for ${branch}: ${details}`);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
821
|
+
const countLabel =
|
|
822
|
+
unpushedCount > 0
|
|
823
|
+
? ` (${unpushedCount} ${
|
|
824
|
+
unpushedCount === 1 ? "commit" : "commits"
|
|
825
|
+
})`
|
|
826
|
+
: "";
|
|
827
|
+
printWarning(`Unpushed commits detected${countLabel}.`);
|
|
807
828
|
}
|
|
808
829
|
} catch (error) {
|
|
809
830
|
const details = error instanceof Error ? error.message : String(error);
|
|
810
831
|
printWarning(`Failed to check git status after session: ${details}`);
|
|
811
832
|
}
|
|
812
833
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
} else {
|
|
816
|
-
// Small buffer before returning to branch list to avoid abrupt screen swap
|
|
817
|
-
await new Promise((resolve) =>
|
|
818
|
-
setTimeout(resolve, POST_SESSION_DELAY_MS),
|
|
819
|
-
);
|
|
820
|
-
}
|
|
834
|
+
// Small buffer before returning to branch list to avoid abrupt screen swap
|
|
835
|
+
await new Promise((resolve) => setTimeout(resolve, POST_SESSION_DELAY_MS));
|
|
821
836
|
printInfo("Session completed successfully. Returning to main menu...");
|
|
822
837
|
return;
|
|
823
838
|
} catch (error) {
|