@akiojin/gwt 4.1.0 → 4.2.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.md +28 -3
- package/dist/claude.d.ts +2 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +2 -0
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +8 -5
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +3 -1
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +13 -2
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/utils/modelOptions.d.ts +4 -0
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.js +19 -0
- package/dist/cli/ui/utils/modelOptions.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -15
- package/dist/index.js.map +1 -1
- package/dist/utils/prompt.d.ts +12 -0
- package/dist/utils/prompt.d.ts.map +1 -1
- package/dist/utils/prompt.js +60 -10
- package/dist/utils/prompt.js.map +1 -1
- package/dist/worktree.d.ts +14 -0
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +33 -2
- package/dist/worktree.js.map +1 -1
- package/package.json +2 -2
- package/src/claude.ts +2 -0
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +2 -1
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +38 -8
- package/src/cli/ui/__tests__/components/App.test.tsx +4 -3
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +1 -0
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +45 -0
- package/src/cli/ui/components/App.tsx +15 -4
- package/src/cli/ui/components/common/Select.tsx +14 -1
- package/src/cli/ui/utils/modelOptions.test.ts +12 -0
- package/src/cli/ui/utils/modelOptions.ts +19 -0
- package/src/index.ts +70 -14
- package/src/utils/__tests__/prompt.test.ts +72 -35
- package/src/utils/prompt.ts +79 -10
- package/src/worktree.ts +48 -1
package/src/utils/prompt.ts
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
import { getTerminalStreams } from "./terminal.js";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
type ReadlinePromptOptions<T> = {
|
|
5
|
+
fallback: T;
|
|
6
|
+
onAnswer: (answer: string) => T | undefined;
|
|
7
|
+
shouldSkip?: (terminal: ReturnType<typeof getTerminalStreams>) => boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
async function runReadlinePrompt<T>(
|
|
11
|
+
promptText: string,
|
|
12
|
+
{ fallback, onAnswer, shouldSkip }: ReadlinePromptOptions<T>,
|
|
13
|
+
): Promise<T> {
|
|
9
14
|
const terminal = getTerminalStreams();
|
|
10
15
|
const stdin = terminal.stdin as NodeJS.ReadStream | undefined;
|
|
11
16
|
const stdout = terminal.stdout as NodeJS.WriteStream | undefined;
|
|
12
17
|
|
|
13
18
|
if (!stdin || typeof stdin.on !== "function" || !stdin.isTTY) {
|
|
14
|
-
return;
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (shouldSkip?.(terminal)) {
|
|
23
|
+
return fallback;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
terminal.exitRawMode?.();
|
|
@@ -30,10 +39,15 @@ export async function waitForEnter(promptMessage: string): Promise<void> {
|
|
|
30
39
|
}
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
return new Promise<T>((resolve) => {
|
|
34
43
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
44
|
+
let finished = false;
|
|
35
45
|
|
|
36
46
|
const cleanup = () => {
|
|
47
|
+
if (finished) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
finished = true;
|
|
37
51
|
rl.removeAllListeners();
|
|
38
52
|
rl.close();
|
|
39
53
|
const remover = (method: "off" | "removeListener") =>
|
|
@@ -61,7 +75,7 @@ export async function waitForEnter(promptMessage: string): Promise<void> {
|
|
|
61
75
|
|
|
62
76
|
const onEnd = () => {
|
|
63
77
|
cleanup();
|
|
64
|
-
resolve();
|
|
78
|
+
resolve(fallback);
|
|
65
79
|
};
|
|
66
80
|
|
|
67
81
|
rl.on("SIGINT", () => {
|
|
@@ -69,12 +83,67 @@ export async function waitForEnter(promptMessage: string): Promise<void> {
|
|
|
69
83
|
process.exit(0);
|
|
70
84
|
});
|
|
71
85
|
|
|
72
|
-
rl.question(`${
|
|
86
|
+
rl.question(`${promptText}\n`, (answer) => {
|
|
87
|
+
const result = onAnswer(answer);
|
|
73
88
|
cleanup();
|
|
74
|
-
resolve();
|
|
89
|
+
resolve(result !== undefined ? result : fallback);
|
|
75
90
|
});
|
|
76
91
|
|
|
77
92
|
stdin.once("end", onEnd);
|
|
78
93
|
stdin.once("error", onEnd);
|
|
79
94
|
});
|
|
80
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Wait for Enter using the same terminal streams as Ink.
|
|
99
|
+
* Falls back to no-op on non-interactive stdin to avoid blocking pipelines.
|
|
100
|
+
*/
|
|
101
|
+
export async function waitForEnter(promptMessage: string): Promise<void> {
|
|
102
|
+
await runReadlinePrompt(promptMessage, {
|
|
103
|
+
fallback: undefined,
|
|
104
|
+
onAnswer: () => undefined,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Prompts the user for a yes/no confirmation in the terminal.
|
|
110
|
+
* Falls back to the default value on non-interactive stdin or fallback terminals.
|
|
111
|
+
*
|
|
112
|
+
* @param promptMessage - The message to display to the user
|
|
113
|
+
* @param options - Configuration options
|
|
114
|
+
* @param options.defaultValue - The default value when input is empty or stdin is non-interactive
|
|
115
|
+
* @returns A promise that resolves to true for yes, false for no
|
|
116
|
+
*/
|
|
117
|
+
export async function confirmYesNo(
|
|
118
|
+
promptMessage: string,
|
|
119
|
+
options: { defaultValue?: boolean } = {},
|
|
120
|
+
): Promise<boolean> {
|
|
121
|
+
const fallback = options.defaultValue ?? false;
|
|
122
|
+
|
|
123
|
+
const suffix =
|
|
124
|
+
options.defaultValue === undefined
|
|
125
|
+
? "[y/n]"
|
|
126
|
+
: options.defaultValue
|
|
127
|
+
? "[Y/n]"
|
|
128
|
+
: "[y/N]";
|
|
129
|
+
|
|
130
|
+
const promptText = `${promptMessage} ${suffix}`.trim();
|
|
131
|
+
|
|
132
|
+
return runReadlinePrompt(promptText, {
|
|
133
|
+
fallback,
|
|
134
|
+
shouldSkip: (terminal) => terminal.usingFallback,
|
|
135
|
+
onAnswer: (answer) => {
|
|
136
|
+
const normalized = answer.trim().toLowerCase();
|
|
137
|
+
if (normalized === "y" || normalized === "yes") {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
if (normalized === "n" || normalized === "no") {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
if (normalized.length === 0 && options.defaultValue !== undefined) {
|
|
144
|
+
return options.defaultValue;
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
package/src/worktree.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
ensureGitignoreEntry,
|
|
22
22
|
branchExists,
|
|
23
23
|
getCurrentBranch,
|
|
24
|
+
getCurrentBranchName,
|
|
24
25
|
} from "./git.js";
|
|
25
26
|
import { getConfig } from "./config/index.js";
|
|
26
27
|
import { GIT_CONFIG } from "./config/constants.js";
|
|
@@ -195,9 +196,55 @@ export async function listAdditionalWorktrees(): Promise<WorktreeInfo[]> {
|
|
|
195
196
|
export async function worktreeExists(
|
|
196
197
|
branchName: string,
|
|
197
198
|
): Promise<string | null> {
|
|
199
|
+
const resolution = await resolveWorktreePathForBranch(branchName);
|
|
200
|
+
return resolution.path;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Resolution result for a branch-associated worktree path.
|
|
205
|
+
*/
|
|
206
|
+
export interface WorktreePathResolution {
|
|
207
|
+
path: string | null;
|
|
208
|
+
mismatch?: {
|
|
209
|
+
path: string;
|
|
210
|
+
actualBranch: string | null;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Resolve a worktree path for the selected branch and verify the actual checkout.
|
|
216
|
+
*/
|
|
217
|
+
export async function resolveWorktreePathForBranch(
|
|
218
|
+
branchName: string,
|
|
219
|
+
): Promise<WorktreePathResolution> {
|
|
198
220
|
const worktrees = await listWorktrees();
|
|
199
221
|
const worktree = worktrees.find((w) => w.branch === branchName);
|
|
200
|
-
|
|
222
|
+
if (!worktree) {
|
|
223
|
+
return { path: null };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const actualBranch = (await getCurrentBranchName(worktree.path)).trim();
|
|
228
|
+
if (!actualBranch || actualBranch !== branchName) {
|
|
229
|
+
return {
|
|
230
|
+
path: null,
|
|
231
|
+
mismatch: {
|
|
232
|
+
path: worktree.path,
|
|
233
|
+
actualBranch: actualBranch || null,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
return {
|
|
239
|
+
path: null,
|
|
240
|
+
mismatch: {
|
|
241
|
+
path: worktree.path,
|
|
242
|
+
actualBranch: null,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { path: worktree.path };
|
|
201
248
|
}
|
|
202
249
|
|
|
203
250
|
export async function generateWorktreePath(
|