@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.
@@ -2,16 +2,88 @@
2
2
  import {
3
3
  createStore,
4
4
  getGlobalConfig
5
- } from "./chunk-FNAMNRUH.js";
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 = execFileSync("git", ["worktree", "list", "--porcelain"], {
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 = execFileSync("git", ["worktree", "list", "--porcelain"], {
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 = execFileSync("sh", ["-c", script], {
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
- execFileSync(
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
- execFileSync("git", ["worktree", "add", worktreePath, branch], {
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
- execFileSync(
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
- execFileSync("git", ["worktree", "prune"], {
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 = execFileSync("git", ["status", "--short"], {
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 = execFileSync("git", ["branch", "--list", branch], {
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 = execFileSync(
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
- execFileSync(
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
- execFileSync("git", ["fetch", remote], {
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 buildListLayout(items, selectedIndex, query, mode) {
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("\u2195 navigate \xB7 Enter open \xB7 D delete \xB7 C create \xB7 A agent \xB7 Q quit")
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, viewportHeight, bodyLength) {
290
- const maxOffset = Math.max(0, bodyLength - viewportHeight);
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 + viewportHeight) next = span.end - viewportHeight + 1;
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, viewportHeight) {
444
+ function composeView(layout, offset, viewportHeight2) {
297
445
  const { header, body, footer } = layout;
298
- const visible = body.slice(offset, offset + viewportHeight);
299
- while (visible.length < viewportHeight) visible.push("");
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 + viewportHeight < body.length ? pc.dim(" \u2193 more") : "";
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(filtered, selectedIndex, query, mode);
473
- const viewport = Math.max(1, rows - fixedHeight(layout));
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.0").action(async () => {
7
- const { runList } = await import("./list-XHV4ODXW.js");
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-XKF574AL.js");
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-Z3YCY245.js");
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-RFATE2PF.js");
31
+ const { printConfigPath } = await import("./config-QSYG3JDC.js");
27
32
  printConfigPath();
28
33
  } else {
29
- const { openConfig } = await import("./config-RFATE2PF.js");
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-MVKLVB5V.js");
39
+ const { printSkill } = await import("./skill-T5VOI4ZB.js");
35
40
  printSkill();
36
41
  });
37
42
  await program.parseAsync(process.argv);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getConfigFilePath
4
- } from "./chunk-FNAMNRUH.js";
4
+ } from "./chunk-OUWQ6NIV.js";
5
5
 
6
6
  // src/commands/config.ts
7
7
  import { spawn } from "child_process";
@@ -4,9 +4,9 @@ import {
4
4
  openConfiguredIde,
5
5
  prepareWorktree,
6
6
  promptExistingWorktree
7
- } from "./chunk-QGSJG72F.js";
8
- import "./chunk-GHYUCETL.js";
9
- import "./chunk-FNAMNRUH.js";
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
+ };