@cestoliv/wt 0.5.0 → 0.5.1
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 +51 -12
- package/SKILL.md +39 -8
- package/dist/{agent-Z3YCY245.js → agent-QYE5UNA3.js} +17 -6
- package/dist/chunk-BJDZXGOC.js +271 -0
- package/dist/{chunk-QGSJG72F.js → chunk-OA55NRNT.js} +2 -2
- package/dist/{chunk-FNAMNRUH.js → chunk-OUWQ6NIV.js} +3 -1
- package/dist/{chunk-GHYUCETL.js → chunk-XOP26UY4.js} +272 -28
- package/dist/cli.js +14 -9
- package/dist/{config-RFATE2PF.js → config-QSYG3JDC.js} +1 -1
- package/dist/{create-XKF574AL.js → create-K4OQIX7A.js} +3 -3
- package/dist/list-GLLMKIKE.js +19 -0
- package/dist/prune-HKCDPXQD.js +31 -0
- package/dist/skill-T5VOI4ZB.js +9 -0
- package/package.json +1 -1
- package/dist/list-XHV4ODXW.js +0 -204
- package/dist/skill-MVKLVB5V.js +0 -9
|
@@ -2,16 +2,88 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createStore,
|
|
4
4
|
getGlobalConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-OUWQ6NIV.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/git.ts
|
|
8
|
-
import { execFileSync } from "child_process";
|
|
8
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
9
9
|
import { existsSync, realpathSync, rmSync } from "fs";
|
|
10
10
|
import path from "path";
|
|
11
|
+
|
|
12
|
+
// src/lib/forge.ts
|
|
13
|
+
import { execFileSync } from "child_process";
|
|
14
|
+
function parseRemoteHost(url) {
|
|
15
|
+
const u = url.trim();
|
|
16
|
+
if (!u) return null;
|
|
17
|
+
const scp = u.match(/^(?:[^@/]+@)?([^:/]+):(?!\/\/)/);
|
|
18
|
+
if (scp) return scp[1].toLowerCase();
|
|
19
|
+
const schemed = u.match(/^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]+@)?([^:/]+)/i);
|
|
20
|
+
if (schemed) return schemed[1].toLowerCase();
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function selectForgeTool(host) {
|
|
24
|
+
if (!host) return null;
|
|
25
|
+
if (host.startsWith("github.") || host.endsWith(".github.com")) return "gh";
|
|
26
|
+
return "glab";
|
|
27
|
+
}
|
|
28
|
+
function buildMergedQuery(tool, branch) {
|
|
29
|
+
if (tool === "gh") {
|
|
30
|
+
return [
|
|
31
|
+
"pr",
|
|
32
|
+
"list",
|
|
33
|
+
"--head",
|
|
34
|
+
branch,
|
|
35
|
+
"--state",
|
|
36
|
+
"merged",
|
|
37
|
+
"--json",
|
|
38
|
+
"number"
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return ["mr", "list", "--merged", "--source-branch", branch, "-F", "json"];
|
|
42
|
+
}
|
|
43
|
+
function parseMergedResult(stdout) {
|
|
44
|
+
try {
|
|
45
|
+
const data = JSON.parse(stdout);
|
|
46
|
+
return Array.isArray(data) && data.length > 0;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var defaultRunner = {
|
|
52
|
+
remoteUrl(repoRoot, remote) {
|
|
53
|
+
return execFileSync("git", ["remote", "get-url", remote], {
|
|
54
|
+
cwd: repoRoot,
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
stdio: "pipe"
|
|
57
|
+
}).trim();
|
|
58
|
+
},
|
|
59
|
+
query(repoRoot, tool, args) {
|
|
60
|
+
return execFileSync(tool, args, {
|
|
61
|
+
cwd: repoRoot,
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
stdio: "pipe",
|
|
64
|
+
timeout: 15e3
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function hasMergedPullRequest(repoRoot, branch, remote = "origin", runner = defaultRunner) {
|
|
69
|
+
try {
|
|
70
|
+
const tool = selectForgeTool(
|
|
71
|
+
parseRemoteHost(runner.remoteUrl(repoRoot, remote))
|
|
72
|
+
);
|
|
73
|
+
if (!tool) return false;
|
|
74
|
+
return parseMergedResult(
|
|
75
|
+
runner.query(repoRoot, tool, buildMergedQuery(tool, branch))
|
|
76
|
+
);
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/lib/git.ts
|
|
11
83
|
function getRepoRoot(cwd = process.cwd()) {
|
|
12
84
|
try {
|
|
13
85
|
const realCwd = realpathSync(cwd);
|
|
14
|
-
const output =
|
|
86
|
+
const output = execFileSync2("git", ["worktree", "list", "--porcelain"], {
|
|
15
87
|
cwd: realCwd,
|
|
16
88
|
encoding: "utf8",
|
|
17
89
|
stdio: "pipe"
|
|
@@ -25,7 +97,7 @@ function getRepoRoot(cwd = process.cwd()) {
|
|
|
25
97
|
function listWorktrees(repoRoot, cwd = process.cwd()) {
|
|
26
98
|
const realRepoRoot = realpathSync(repoRoot);
|
|
27
99
|
const realCwd = realpathSync(cwd);
|
|
28
|
-
const output =
|
|
100
|
+
const output = execFileSync2("git", ["worktree", "list", "--porcelain"], {
|
|
29
101
|
cwd: realRepoRoot,
|
|
30
102
|
encoding: "utf8"
|
|
31
103
|
});
|
|
@@ -35,7 +107,7 @@ function listWorktrees(repoRoot, cwd = process.cwd()) {
|
|
|
35
107
|
).join('; echo "---SEP---"; ');
|
|
36
108
|
let commits = [];
|
|
37
109
|
try {
|
|
38
|
-
const batchOutput =
|
|
110
|
+
const batchOutput = execFileSync2("sh", ["-c", script], {
|
|
39
111
|
encoding: "utf8",
|
|
40
112
|
timeout: 8e3
|
|
41
113
|
});
|
|
@@ -48,7 +120,7 @@ function listWorktrees(repoRoot, cwd = process.cwd()) {
|
|
|
48
120
|
}));
|
|
49
121
|
}
|
|
50
122
|
function parseWorktreeList(output, repoRoot, cwd) {
|
|
51
|
-
return output.trim().split("\n\n").map((block) => {
|
|
123
|
+
return output.trim().split("\n\n").map((block, index) => {
|
|
52
124
|
const lines = block.trim().split("\n");
|
|
53
125
|
const wtPath = lines[0].slice("worktree ".length);
|
|
54
126
|
const branchLine = lines.find((l) => l.startsWith("branch "));
|
|
@@ -57,13 +129,15 @@ function parseWorktreeList(output, repoRoot, cwd) {
|
|
|
57
129
|
path: wtPath,
|
|
58
130
|
branch,
|
|
59
131
|
isCurrent: cwd === wtPath || cwd.startsWith(wtPath + path.sep),
|
|
132
|
+
// The main worktree is always the first entry of `git worktree list`.
|
|
133
|
+
isMain: index === 0,
|
|
60
134
|
repoRoot
|
|
61
135
|
};
|
|
62
136
|
});
|
|
63
137
|
}
|
|
64
138
|
function addWorktree(repoRoot, worktreePath, branch, baseBranch) {
|
|
65
139
|
if (baseBranch) {
|
|
66
|
-
|
|
140
|
+
execFileSync2(
|
|
67
141
|
"git",
|
|
68
142
|
["worktree", "add", "-b", branch, worktreePath, baseBranch],
|
|
69
143
|
{
|
|
@@ -71,14 +145,24 @@ function addWorktree(repoRoot, worktreePath, branch, baseBranch) {
|
|
|
71
145
|
}
|
|
72
146
|
);
|
|
73
147
|
} else {
|
|
74
|
-
|
|
148
|
+
execFileSync2("git", ["worktree", "add", worktreePath, branch], {
|
|
75
149
|
cwd: repoRoot
|
|
76
150
|
});
|
|
77
151
|
}
|
|
78
152
|
}
|
|
79
153
|
function removeWorktree(repoRoot, worktreePath, force = false) {
|
|
154
|
+
const resolve = (p) => {
|
|
155
|
+
try {
|
|
156
|
+
return realpathSync(p);
|
|
157
|
+
} catch {
|
|
158
|
+
return p;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
if (resolve(worktreePath) === resolve(repoRoot)) {
|
|
162
|
+
throw new Error("Refusing to remove the main worktree");
|
|
163
|
+
}
|
|
80
164
|
try {
|
|
81
|
-
|
|
165
|
+
execFileSync2(
|
|
82
166
|
"git",
|
|
83
167
|
["worktree", "remove", ...force ? ["--force"] : [], worktreePath],
|
|
84
168
|
{ cwd: repoRoot, stdio: "pipe" }
|
|
@@ -88,7 +172,7 @@ function removeWorktree(repoRoot, worktreePath, force = false) {
|
|
|
88
172
|
if (existsSync(worktreePath)) {
|
|
89
173
|
rmSync(worktreePath, { recursive: true, force: true });
|
|
90
174
|
}
|
|
91
|
-
|
|
175
|
+
execFileSync2("git", ["worktree", "prune"], {
|
|
92
176
|
cwd: repoRoot,
|
|
93
177
|
stdio: "pipe"
|
|
94
178
|
});
|
|
@@ -96,7 +180,7 @@ function removeWorktree(repoRoot, worktreePath, force = false) {
|
|
|
96
180
|
}
|
|
97
181
|
function listWorktreeDirtyFiles(worktreePath) {
|
|
98
182
|
try {
|
|
99
|
-
const out =
|
|
183
|
+
const out = execFileSync2("git", ["status", "--short"], {
|
|
100
184
|
cwd: worktreePath,
|
|
101
185
|
encoding: "utf8"
|
|
102
186
|
});
|
|
@@ -107,12 +191,12 @@ function listWorktreeDirtyFiles(worktreePath) {
|
|
|
107
191
|
}
|
|
108
192
|
function branchExists(repoRoot, branch) {
|
|
109
193
|
try {
|
|
110
|
-
const local =
|
|
194
|
+
const local = execFileSync2("git", ["branch", "--list", branch], {
|
|
111
195
|
cwd: repoRoot,
|
|
112
196
|
encoding: "utf8"
|
|
113
197
|
}).trim();
|
|
114
198
|
if (local) return true;
|
|
115
|
-
const remote =
|
|
199
|
+
const remote = execFileSync2(
|
|
116
200
|
"git",
|
|
117
201
|
["ls-remote", "--heads", "origin", branch],
|
|
118
202
|
{
|
|
@@ -126,9 +210,55 @@ function branchExists(repoRoot, branch) {
|
|
|
126
210
|
return false;
|
|
127
211
|
}
|
|
128
212
|
}
|
|
213
|
+
function isAncestor(repoRoot, ancestor, descendant) {
|
|
214
|
+
try {
|
|
215
|
+
execFileSync2("git", ["merge-base", "--is-ancestor", ancestor, descendant], {
|
|
216
|
+
cwd: repoRoot,
|
|
217
|
+
stdio: "pipe"
|
|
218
|
+
});
|
|
219
|
+
return true;
|
|
220
|
+
} catch {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function hasRemoteTrackingRef(repoRoot, remote, branch) {
|
|
225
|
+
try {
|
|
226
|
+
execFileSync2(
|
|
227
|
+
"git",
|
|
228
|
+
["rev-parse", "--verify", "--quiet", `refs/remotes/${remote}/${branch}`],
|
|
229
|
+
{ cwd: repoRoot, stdio: "pipe" }
|
|
230
|
+
);
|
|
231
|
+
return true;
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function isBranchMerged(repoRoot, branch, baseBranch, forgeCheck = hasMergedPullRequest) {
|
|
237
|
+
try {
|
|
238
|
+
const out = execFileSync2("git", ["cherry", baseBranch, branch], {
|
|
239
|
+
cwd: repoRoot,
|
|
240
|
+
encoding: "utf8",
|
|
241
|
+
stdio: "pipe"
|
|
242
|
+
});
|
|
243
|
+
const lines = out.split("\n").filter((l) => l.trim().length > 0);
|
|
244
|
+
if (lines.length > 0 && lines.every((l) => l.startsWith("-"))) return true;
|
|
245
|
+
const revParse = (ref) => execFileSync2("git", ["rev-parse", ref], {
|
|
246
|
+
cwd: repoRoot,
|
|
247
|
+
encoding: "utf8",
|
|
248
|
+
stdio: "pipe"
|
|
249
|
+
}).trim();
|
|
250
|
+
if (revParse(branch) === revParse(baseBranch)) return false;
|
|
251
|
+
if (!isAncestor(repoRoot, branch, baseBranch)) return false;
|
|
252
|
+
const remote = baseBranch.includes("/") ? baseBranch.split("/", 1)[0] : "origin";
|
|
253
|
+
if (!hasRemoteTrackingRef(repoRoot, remote, branch)) return false;
|
|
254
|
+
return forgeCheck(repoRoot, branch, remote);
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
129
259
|
function setUpstreamTracking(worktreePath, branch, remote = "origin") {
|
|
130
260
|
try {
|
|
131
|
-
|
|
261
|
+
execFileSync2(
|
|
132
262
|
"git",
|
|
133
263
|
["branch", "--set-upstream-to", `${remote}/${branch}`, branch],
|
|
134
264
|
{ cwd: worktreePath, stdio: "pipe" }
|
|
@@ -137,7 +267,7 @@ function setUpstreamTracking(worktreePath, branch, remote = "origin") {
|
|
|
137
267
|
}
|
|
138
268
|
}
|
|
139
269
|
function fetchRemote(repoRoot, remote = "origin") {
|
|
140
|
-
|
|
270
|
+
execFileSync2("git", ["fetch", remote], {
|
|
141
271
|
cwd: repoRoot,
|
|
142
272
|
stdio: "pipe",
|
|
143
273
|
timeout: 3e4
|
|
@@ -253,8 +383,24 @@ function shortenPath(p) {
|
|
|
253
383
|
const home = process.env.HOME ?? "";
|
|
254
384
|
return home && p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
255
385
|
}
|
|
256
|
-
function
|
|
386
|
+
function formatRefreshStatus(lastRefresh, intervalMinutes) {
|
|
387
|
+
const time = lastRefresh.toLocaleTimeString();
|
|
388
|
+
return `\u27F3 Last refreshed ${time} \xB7 every ${intervalMinutes}m`;
|
|
389
|
+
}
|
|
390
|
+
function reconcileSelectedIndex(items, prevPath, prevIndex) {
|
|
391
|
+
if (items.length === 0) return 0;
|
|
392
|
+
if (prevPath) {
|
|
393
|
+
const found = items.findIndex((w) => w.path === prevPath);
|
|
394
|
+
if (found !== -1) return found;
|
|
395
|
+
}
|
|
396
|
+
return Math.min(Math.max(0, prevIndex), items.length - 1);
|
|
397
|
+
}
|
|
398
|
+
function buildListLayout(items, selectedIndex, query, mode, lastRefresh = null, intervalMinutes = 0) {
|
|
257
399
|
const header = [];
|
|
400
|
+
if (lastRefresh && intervalMinutes > 0) {
|
|
401
|
+
header.push(pc.dim(formatRefreshStatus(lastRefresh, intervalMinutes)));
|
|
402
|
+
header.push("");
|
|
403
|
+
}
|
|
258
404
|
if (mode === "global") {
|
|
259
405
|
header.push(
|
|
260
406
|
pc.dim("\u2139 Not in a git repository \u2014 showing all registered worktrees")
|
|
@@ -271,7 +417,7 @@ function buildListLayout(items, selectedIndex, query, mode) {
|
|
|
271
417
|
for (const item of groupItems) {
|
|
272
418
|
const start = body.length;
|
|
273
419
|
const cursor = i === selectedIndex ? pc.cyan("\u25B6") : " ";
|
|
274
|
-
const branchLabel = item.isCurrent ? pc.dim(`${item.branch} (current)`) : pc.white(item.branch);
|
|
420
|
+
const branchLabel = item.isCurrent ? pc.dim(`${item.branch} (current)`) : item.isMain ? pc.dim(`${item.branch} (main)`) : pc.white(item.branch);
|
|
275
421
|
const pathLabel = pc.dim(shortenPath(item.path));
|
|
276
422
|
body.push(` ${cursor} ${branchLabel} ${pathLabel}`);
|
|
277
423
|
if (item.lastCommit) {
|
|
@@ -282,28 +428,33 @@ function buildListLayout(items, selectedIndex, query, mode) {
|
|
|
282
428
|
}
|
|
283
429
|
}
|
|
284
430
|
const footer = [
|
|
285
|
-
pc.dim(
|
|
431
|
+
pc.dim(
|
|
432
|
+
"\u2195 navigate \xB7 Enter open \xB7 D delete \xB7 P prune \xB7 C create \xB7 A agent \xB7 Q quit"
|
|
433
|
+
)
|
|
286
434
|
];
|
|
287
435
|
return { header, body, footer, itemSpans };
|
|
288
436
|
}
|
|
289
|
-
function clampScroll(offset, span,
|
|
290
|
-
const maxOffset = Math.max(0, bodyLength -
|
|
437
|
+
function clampScroll(offset, span, viewportHeight2, bodyLength) {
|
|
438
|
+
const maxOffset = Math.max(0, bodyLength - viewportHeight2);
|
|
291
439
|
let next = offset;
|
|
292
440
|
if (span.start < next) next = span.start;
|
|
293
|
-
if (span.end >= next +
|
|
441
|
+
if (span.end >= next + viewportHeight2) next = span.end - viewportHeight2 + 1;
|
|
294
442
|
return Math.max(0, Math.min(next, maxOffset));
|
|
295
443
|
}
|
|
296
|
-
function composeView(layout, offset,
|
|
444
|
+
function composeView(layout, offset, viewportHeight2) {
|
|
297
445
|
const { header, body, footer } = layout;
|
|
298
|
-
const visible = body.slice(offset, offset +
|
|
299
|
-
while (visible.length <
|
|
446
|
+
const visible = body.slice(offset, offset + viewportHeight2);
|
|
447
|
+
while (visible.length < viewportHeight2) visible.push("");
|
|
300
448
|
const topSlot = offset > 0 ? pc.dim(" \u2191 more") : "";
|
|
301
|
-
const bottomSlot = offset +
|
|
449
|
+
const bottomSlot = offset + viewportHeight2 < body.length ? pc.dim(" \u2193 more") : "";
|
|
302
450
|
return [...header, topSlot, ...visible, bottomSlot, ...footer].join("\n");
|
|
303
451
|
}
|
|
304
452
|
function fixedHeight(layout) {
|
|
305
453
|
return layout.header.length + layout.footer.length + 2;
|
|
306
454
|
}
|
|
455
|
+
function viewportHeight(layout, rows) {
|
|
456
|
+
return Math.max(1, rows - fixedHeight(layout) - 1);
|
|
457
|
+
}
|
|
307
458
|
function setupRawMode() {
|
|
308
459
|
process.stdin.setRawMode(true);
|
|
309
460
|
process.stdin.resume();
|
|
@@ -314,6 +465,17 @@ function cleanupRawMode() {
|
|
|
314
465
|
process.stdin.pause();
|
|
315
466
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
316
467
|
}
|
|
468
|
+
function waitForKeypress() {
|
|
469
|
+
return new Promise((resolve) => {
|
|
470
|
+
process.stdin.setRawMode(true);
|
|
471
|
+
process.stdin.resume();
|
|
472
|
+
process.stdin.once("data", () => {
|
|
473
|
+
process.stdin.setRawMode(false);
|
|
474
|
+
process.stdin.pause();
|
|
475
|
+
resolve();
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
}
|
|
317
479
|
function renderRepoPicker(repos, selectedIndex, query) {
|
|
318
480
|
const lines = [];
|
|
319
481
|
lines.push(pc.dim("\u2139 Not in a git repository \u2014 select a repo to create in"));
|
|
@@ -462,15 +624,25 @@ async function runWizard(steps) {
|
|
|
462
624
|
}
|
|
463
625
|
return true;
|
|
464
626
|
}
|
|
465
|
-
async function runInteractiveList(allItems, mode, handlers) {
|
|
627
|
+
async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
628
|
+
const { autoRefreshMinutes = 0 } = options;
|
|
629
|
+
const refreshEnabled = Number.isFinite(autoRefreshMinutes) && autoRefreshMinutes > 0;
|
|
466
630
|
let query = "";
|
|
467
631
|
let selectedIndex = 0;
|
|
468
632
|
let scrollOffset = 0;
|
|
469
633
|
let filtered = allItems;
|
|
634
|
+
let lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : null;
|
|
470
635
|
const render = () => {
|
|
471
636
|
const rows = process.stdout.rows ?? 24;
|
|
472
|
-
const layout = buildListLayout(
|
|
473
|
-
|
|
637
|
+
const layout = buildListLayout(
|
|
638
|
+
filtered,
|
|
639
|
+
selectedIndex,
|
|
640
|
+
query,
|
|
641
|
+
mode,
|
|
642
|
+
lastRefresh,
|
|
643
|
+
autoRefreshMinutes
|
|
644
|
+
);
|
|
645
|
+
const viewport = viewportHeight(layout, rows);
|
|
474
646
|
const span = layout.itemSpans[selectedIndex] ?? { start: 0, end: 0 };
|
|
475
647
|
scrollOffset = clampScroll(
|
|
476
648
|
scrollOffset,
|
|
@@ -485,6 +657,9 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
485
657
|
render();
|
|
486
658
|
return new Promise((resolve, reject) => {
|
|
487
659
|
let listenerActive = false;
|
|
660
|
+
let interacting = false;
|
|
661
|
+
let refreshing = false;
|
|
662
|
+
let refreshTimer = null;
|
|
488
663
|
const attachListener = () => {
|
|
489
664
|
if (!listenerActive) {
|
|
490
665
|
process.stdin.on("data", onData);
|
|
@@ -497,9 +672,35 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
497
672
|
process.stdout.removeListener("resize", render);
|
|
498
673
|
listenerActive = false;
|
|
499
674
|
};
|
|
675
|
+
const stopRefresh = () => {
|
|
676
|
+
if (refreshTimer !== null) {
|
|
677
|
+
clearInterval(refreshTimer);
|
|
678
|
+
refreshTimer = null;
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
const tick = async () => {
|
|
682
|
+
if (interacting || refreshing) return;
|
|
683
|
+
refreshing = true;
|
|
684
|
+
try {
|
|
685
|
+
const prevPath = filtered[selectedIndex]?.path;
|
|
686
|
+
allItems = await handlers.refreshItems();
|
|
687
|
+
filtered = filterItems(allItems, query);
|
|
688
|
+
selectedIndex = reconcileSelectedIndex(
|
|
689
|
+
filtered,
|
|
690
|
+
prevPath,
|
|
691
|
+
selectedIndex
|
|
692
|
+
);
|
|
693
|
+
lastRefresh = /* @__PURE__ */ new Date();
|
|
694
|
+
if (!interacting) render();
|
|
695
|
+
} catch {
|
|
696
|
+
} finally {
|
|
697
|
+
refreshing = false;
|
|
698
|
+
}
|
|
699
|
+
};
|
|
500
700
|
const onData = async (key) => {
|
|
501
701
|
try {
|
|
502
702
|
if (key === "" || key === "q" || key === "Q" || key === "\x1B") {
|
|
703
|
+
stopRefresh();
|
|
503
704
|
detachListener();
|
|
504
705
|
cleanupRawMode();
|
|
505
706
|
resolve();
|
|
@@ -512,6 +713,7 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
512
713
|
} else if (key === "\r") {
|
|
513
714
|
const item = filtered[selectedIndex];
|
|
514
715
|
if (item) {
|
|
716
|
+
stopRefresh();
|
|
515
717
|
detachListener();
|
|
516
718
|
cleanupRawMode();
|
|
517
719
|
handlers.onOpen(item);
|
|
@@ -520,12 +722,21 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
520
722
|
} else if (key === "d" || key === "D") {
|
|
521
723
|
const item = filtered[selectedIndex];
|
|
522
724
|
if (item) {
|
|
725
|
+
if (item.isMain) {
|
|
726
|
+
process.stdout.write(
|
|
727
|
+
pc.red(
|
|
728
|
+
"\nCannot delete the main repository \u2014 only worktrees can be removed.\n"
|
|
729
|
+
)
|
|
730
|
+
);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
523
733
|
if (item.isCurrent) {
|
|
524
734
|
process.stdout.write(
|
|
525
735
|
pc.red("\nCannot delete the worktree you are currently in.\n")
|
|
526
736
|
);
|
|
527
737
|
return;
|
|
528
738
|
}
|
|
739
|
+
interacting = true;
|
|
529
740
|
detachListener();
|
|
530
741
|
cleanupRawMode();
|
|
531
742
|
const confirmed = await handlers.onDelete(item);
|
|
@@ -539,9 +750,32 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
539
750
|
);
|
|
540
751
|
setupRawMode();
|
|
541
752
|
attachListener();
|
|
753
|
+
interacting = false;
|
|
542
754
|
render();
|
|
543
755
|
}
|
|
756
|
+
} else if (key === "p" || key === "P") {
|
|
757
|
+
interacting = true;
|
|
758
|
+
detachListener();
|
|
759
|
+
cleanupRawMode();
|
|
760
|
+
const removed = await handlers.onWipe(allItems);
|
|
761
|
+
if (removed.length > 0) {
|
|
762
|
+
const removedSet = new Set(removed);
|
|
763
|
+
allItems = allItems.filter((w) => !removedSet.has(w));
|
|
764
|
+
filtered = filtered.filter((w) => !removedSet.has(w));
|
|
765
|
+
} else {
|
|
766
|
+
process.stdout.write(pc.dim("\nPress any key to continue\u2026"));
|
|
767
|
+
await waitForKeypress();
|
|
768
|
+
}
|
|
769
|
+
selectedIndex = Math.min(
|
|
770
|
+
selectedIndex,
|
|
771
|
+
Math.max(0, filtered.length - 1)
|
|
772
|
+
);
|
|
773
|
+
setupRawMode();
|
|
774
|
+
attachListener();
|
|
775
|
+
interacting = false;
|
|
776
|
+
render();
|
|
544
777
|
} else if (key === "c" || key === "C") {
|
|
778
|
+
interacting = true;
|
|
545
779
|
detachListener();
|
|
546
780
|
cleanupRawMode();
|
|
547
781
|
await handlers.onCreate();
|
|
@@ -551,10 +785,13 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
551
785
|
selectedIndex,
|
|
552
786
|
Math.max(0, filtered.length - 1)
|
|
553
787
|
);
|
|
788
|
+
lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : lastRefresh;
|
|
554
789
|
setupRawMode();
|
|
555
790
|
attachListener();
|
|
791
|
+
interacting = false;
|
|
556
792
|
render();
|
|
557
793
|
} else if (key === "a" || key === "A") {
|
|
794
|
+
interacting = true;
|
|
558
795
|
detachListener();
|
|
559
796
|
cleanupRawMode();
|
|
560
797
|
await handlers.onAgent();
|
|
@@ -564,8 +801,10 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
564
801
|
selectedIndex,
|
|
565
802
|
Math.max(0, filtered.length - 1)
|
|
566
803
|
);
|
|
804
|
+
lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : lastRefresh;
|
|
567
805
|
setupRawMode();
|
|
568
806
|
attachListener();
|
|
807
|
+
interacting = false;
|
|
569
808
|
render();
|
|
570
809
|
} else if (key === "\x7F") {
|
|
571
810
|
query = query.slice(0, -1);
|
|
@@ -581,12 +820,16 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
581
820
|
render();
|
|
582
821
|
}
|
|
583
822
|
} catch (err) {
|
|
823
|
+
stopRefresh();
|
|
584
824
|
detachListener();
|
|
585
825
|
cleanupRawMode();
|
|
586
826
|
reject(err);
|
|
587
827
|
}
|
|
588
828
|
};
|
|
589
829
|
attachListener();
|
|
830
|
+
if (refreshEnabled) {
|
|
831
|
+
refreshTimer = setInterval(tick, autoRefreshMinutes * 6e4);
|
|
832
|
+
}
|
|
590
833
|
});
|
|
591
834
|
}
|
|
592
835
|
|
|
@@ -597,6 +840,7 @@ export {
|
|
|
597
840
|
removeWorktree,
|
|
598
841
|
listWorktreeDirtyFiles,
|
|
599
842
|
branchExists,
|
|
843
|
+
isBranchMerged,
|
|
600
844
|
setUpstreamTracking,
|
|
601
845
|
fetchRemote,
|
|
602
846
|
resolveWorktreePath,
|
package/dist/cli.js
CHANGED
|
@@ -3,35 +3,40 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
var program = new Command();
|
|
6
|
-
program.name("wt").description("Git worktree manager").version("0.5.
|
|
7
|
-
const { runList } = await import("./list-
|
|
6
|
+
program.name("wt").description("Git worktree manager").version("0.5.1").action(async () => {
|
|
7
|
+
const { runList } = await import("./list-GLLMKIKE.js");
|
|
8
8
|
await runList();
|
|
9
9
|
});
|
|
10
10
|
program.command("create [branch]").description("Create a new worktree").action(async (branch) => {
|
|
11
|
-
const { createWorktree } = await import("./create-
|
|
11
|
+
const { createWorktree } = await import("./create-K4OQIX7A.js");
|
|
12
12
|
await createWorktree(branch);
|
|
13
13
|
});
|
|
14
14
|
program.command("agent <branch> <plan_prompt>").description("Create a worktree and auto-start an AI agent in Zed (macOS)").option(
|
|
15
15
|
"--mode <mode>",
|
|
16
|
-
"Claude Code permission mode (default, plan, auto, etc.)"
|
|
17
|
-
"plan"
|
|
16
|
+
"Claude Code permission mode (default, plan, auto, etc.); overrides the configured agent_mode"
|
|
18
17
|
).action(
|
|
19
18
|
async (branch, planPrompt, options) => {
|
|
20
|
-
const { createAgentWorktree } = await import("./agent-
|
|
19
|
+
const { createAgentWorktree } = await import("./agent-QYE5UNA3.js");
|
|
21
20
|
await createAgentWorktree(branch, planPrompt, { mode: options.mode });
|
|
22
21
|
}
|
|
23
22
|
);
|
|
23
|
+
program.command("prune").description(
|
|
24
|
+
"Remove worktrees whose branch has been merged into the base branch"
|
|
25
|
+
).action(async () => {
|
|
26
|
+
const { runPrune } = await import("./prune-HKCDPXQD.js");
|
|
27
|
+
await runPrune();
|
|
28
|
+
});
|
|
24
29
|
program.command("config").description("Open the config file in $EDITOR").option("--path", "Print the config file path and exit").action(async (options) => {
|
|
25
30
|
if (options.path) {
|
|
26
|
-
const { printConfigPath } = await import("./config-
|
|
31
|
+
const { printConfigPath } = await import("./config-QSYG3JDC.js");
|
|
27
32
|
printConfigPath();
|
|
28
33
|
} else {
|
|
29
|
-
const { openConfig } = await import("./config-
|
|
34
|
+
const { openConfig } = await import("./config-QSYG3JDC.js");
|
|
30
35
|
openConfig();
|
|
31
36
|
}
|
|
32
37
|
});
|
|
33
38
|
program.command("skill").description("Print the wt skill file to stdout").action(async () => {
|
|
34
|
-
const { printSkill } = await import("./skill-
|
|
39
|
+
const { printSkill } = await import("./skill-T5VOI4ZB.js");
|
|
35
40
|
printSkill();
|
|
36
41
|
});
|
|
37
42
|
await program.parseAsync(process.argv);
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
openConfiguredIde,
|
|
5
5
|
prepareWorktree,
|
|
6
6
|
promptExistingWorktree
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-OA55NRNT.js";
|
|
8
|
+
import "./chunk-XOP26UY4.js";
|
|
9
|
+
import "./chunk-OUWQ6NIV.js";
|
|
10
10
|
export {
|
|
11
11
|
createWorktree,
|
|
12
12
|
openConfiguredIde,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildMergedPredicate,
|
|
4
|
+
deleteWorktree,
|
|
5
|
+
prepareListItems,
|
|
6
|
+
runList,
|
|
7
|
+
selectWipeCandidates,
|
|
8
|
+
wipeWorktrees
|
|
9
|
+
} from "./chunk-BJDZXGOC.js";
|
|
10
|
+
import "./chunk-XOP26UY4.js";
|
|
11
|
+
import "./chunk-OUWQ6NIV.js";
|
|
12
|
+
export {
|
|
13
|
+
buildMergedPredicate,
|
|
14
|
+
deleteWorktree,
|
|
15
|
+
prepareListItems,
|
|
16
|
+
runList,
|
|
17
|
+
selectWipeCandidates,
|
|
18
|
+
wipeWorktrees
|
|
19
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
prepareListItems,
|
|
4
|
+
wipeWorktrees
|
|
5
|
+
} from "./chunk-BJDZXGOC.js";
|
|
6
|
+
import "./chunk-XOP26UY4.js";
|
|
7
|
+
import {
|
|
8
|
+
createStore
|
|
9
|
+
} from "./chunk-OUWQ6NIV.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/prune.ts
|
|
12
|
+
import pc from "picocolors";
|
|
13
|
+
async function runPrune(options = {}) {
|
|
14
|
+
const { cwd = process.cwd(), store = createStore() } = options;
|
|
15
|
+
const { items, mode } = await prepareListItems({ cwd, store });
|
|
16
|
+
if (items.length === 0) {
|
|
17
|
+
console.log(
|
|
18
|
+
pc.dim(
|
|
19
|
+
mode === "global" ? "No repos registered. Run `wt create` inside a repo to get started." : "No worktrees found."
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const removed = await wipeWorktrees(items, store, { fetch: true });
|
|
25
|
+
if (removed.length > 0) {
|
|
26
|
+
console.log(pc.green(`\u2713 Pruned ${removed.length} worktree(s).`));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
runPrune
|
|
31
|
+
};
|