worktree-launcher 1.2.1 → 1.4.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/index.js CHANGED
@@ -1,82 +1,444 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- confirm,
4
- findWorktree,
5
- getGitRoot,
6
- isBranchMerged,
7
- isGitRepo,
8
- isToolAvailable,
9
- launchAITool,
10
- listWorktrees,
11
- newCommand,
12
- pruneWorktrees,
13
- remoteBranchExists,
14
- removeWorktree,
15
- selectAITool,
16
- selectMultiple
17
- } from "./chunk-KGMGW33P.js";
18
2
 
19
3
  // src/index.ts
20
4
  import { Command } from "commander";
21
5
 
22
- // src/commands/list.ts
6
+ // src/commands/new.ts
23
7
  import chalk from "chalk";
8
+ import ora from "ora";
9
+ import path4 from "path";
10
+
11
+ // src/utils/git.ts
12
+ import { execFile } from "child_process";
13
+ import { promisify } from "util";
24
14
  import path from "path";
25
- async function listCommand() {
15
+ var execFileAsync = promisify(execFile);
16
+ async function isGitRepo() {
17
+ try {
18
+ await execFileAsync("git", ["rev-parse", "--git-dir"]);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+ async function getGitRoot() {
25
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"]);
26
+ return stdout.trim();
27
+ }
28
+ async function branchExists(branchName) {
29
+ try {
30
+ await execFileAsync("git", ["rev-parse", "--verify", branchName]);
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+ async function remoteBranchExists(branchName) {
37
+ try {
38
+ const { stdout } = await execFileAsync("git", ["branch", "-r"]);
39
+ const remoteBranches = stdout.split("\n").map((b) => b.trim());
40
+ return remoteBranches.some(
41
+ (b) => b === `origin/${branchName}` || b.endsWith(`/${branchName}`)
42
+ );
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+ async function getCurrentBranch() {
48
+ try {
49
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
50
+ return stdout.trim();
51
+ } catch {
52
+ return "HEAD";
53
+ }
54
+ }
55
+ async function getDefaultBranch() {
56
+ try {
57
+ const { stdout } = await execFileAsync("git", ["symbolic-ref", "refs/remotes/origin/HEAD"]);
58
+ return stdout.trim().replace("refs/remotes/origin/", "");
59
+ } catch {
60
+ if (await branchExists("main")) return "main";
61
+ if (await branchExists("master")) return "master";
62
+ return "main";
63
+ }
64
+ }
65
+ async function createWorktree(worktreePath, branchName) {
66
+ validateBranchName(branchName);
67
+ const exists = await branchExists(branchName);
68
+ if (exists) {
69
+ await execFileAsync("git", ["worktree", "add", "--", worktreePath, branchName]);
70
+ } else {
71
+ await execFileAsync("git", ["worktree", "add", "-b", branchName, "--", worktreePath]);
72
+ }
73
+ }
74
+ async function listWorktrees() {
75
+ const { stdout } = await execFileAsync("git", ["worktree", "list", "--porcelain"]);
76
+ const worktrees2 = [];
77
+ let current = {};
78
+ for (const line of stdout.split("\n")) {
79
+ if (line.startsWith("worktree ")) {
80
+ if (current.path) {
81
+ worktrees2.push(current);
82
+ }
83
+ current = {
84
+ path: line.substring(9),
85
+ bare: false,
86
+ detached: false
87
+ };
88
+ } else if (line.startsWith("HEAD ")) {
89
+ current.head = line.substring(5);
90
+ } else if (line.startsWith("branch ")) {
91
+ current.branch = line.substring(7).replace("refs/heads/", "");
92
+ } else if (line === "bare") {
93
+ current.bare = true;
94
+ } else if (line === "detached") {
95
+ current.detached = true;
96
+ }
97
+ }
98
+ if (current.path) {
99
+ worktrees2.push(current);
100
+ }
101
+ return worktrees2;
102
+ }
103
+ async function removeWorktree(worktreePath, force = false) {
104
+ const args = ["worktree", "remove"];
105
+ if (force) args.push("--force");
106
+ args.push(worktreePath);
107
+ await execFileAsync("git", args);
108
+ }
109
+ async function pruneWorktrees() {
110
+ await execFileAsync("git", ["worktree", "prune"]);
111
+ }
112
+ async function isBranchMerged(branchName) {
113
+ try {
114
+ const defaultBranch2 = await getDefaultBranch();
115
+ const { stdout } = await execFileAsync("git", ["branch", "--merged", defaultBranch2]);
116
+ const mergedBranches = stdout.split("\n").map((b) => b.trim().replace("* ", ""));
117
+ return mergedBranches.includes(branchName);
118
+ } catch {
119
+ return false;
120
+ }
121
+ }
122
+ function validateBranchName(branchName) {
123
+ if (!branchName || branchName.trim() === "") {
124
+ throw new Error("Branch name cannot be empty");
125
+ }
126
+ if (branchName.startsWith("-")) {
127
+ throw new Error("Branch name cannot start with -");
128
+ }
129
+ if (branchName.includes("..")) {
130
+ throw new Error("Branch name cannot contain ..");
131
+ }
132
+ if (branchName.length > 250) {
133
+ throw new Error("Branch name too long (max 250 characters)");
134
+ }
135
+ }
136
+ function getWorktreePath(mainRepoPath2, branchName) {
137
+ validateBranchName(branchName);
138
+ const repoName = path.basename(mainRepoPath2);
139
+ const safeBranchName = branchName.replace(/\//g, "-");
140
+ return path.join(path.dirname(mainRepoPath2), `${repoName}-${safeBranchName}`);
141
+ }
142
+ async function findWorktree(identifier) {
143
+ const worktrees2 = await listWorktrees();
144
+ return worktrees2.find(
145
+ (wt) => wt.branch === identifier || wt.path === identifier || path.basename(wt.path) === identifier || wt.path.endsWith(identifier)
146
+ );
147
+ }
148
+ async function pushBranch(branchName, cwd) {
149
+ const args = ["push", "-u", "origin", branchName];
150
+ await execFileAsync("git", args, cwd ? { cwd } : void 0);
151
+ }
152
+
153
+ // src/utils/env.ts
154
+ import { glob } from "glob";
155
+ import { copyFile } from "fs/promises";
156
+ import path2 from "path";
157
+ async function findEnvFiles(sourceDir) {
158
+ const files = await glob(".env*", {
159
+ cwd: sourceDir,
160
+ dot: true,
161
+ nodir: true
162
+ });
163
+ return files.filter((file) => {
164
+ if (file !== ".env" && !file.startsWith(".env.")) return false;
165
+ if (file.endsWith(".example") || file.endsWith(".sample") || file.endsWith(".template")) return false;
166
+ return true;
167
+ });
168
+ }
169
+ async function copyEnvFiles(sourceDir, destDir) {
170
+ const envFiles = await findEnvFiles(sourceDir);
171
+ const copied = [];
172
+ for (const file of envFiles) {
173
+ try {
174
+ await copyFile(path2.join(sourceDir, file), path2.join(destDir, file));
175
+ copied.push(file);
176
+ } catch (error) {
177
+ console.warn(`Warning: Could not copy ${file}: ${error}`);
178
+ }
179
+ }
180
+ return copied;
181
+ }
182
+
183
+ // src/utils/launcher.ts
184
+ import { spawn } from "child_process";
185
+ import { access } from "fs/promises";
186
+ import path3 from "path";
187
+ import { constants } from "fs";
188
+ function launchAITool(options) {
189
+ const { cwd, tool } = options;
190
+ spawn(tool, [], {
191
+ cwd,
192
+ stdio: "inherit",
193
+ shell: true
194
+ });
195
+ }
196
+ async function isToolAvailable(tool) {
197
+ return new Promise((resolve) => {
198
+ const child = spawn("which", [tool]);
199
+ child.on("close", (code) => {
200
+ resolve(code === 0);
201
+ });
202
+ child.on("error", () => {
203
+ resolve(false);
204
+ });
205
+ });
206
+ }
207
+ async function detectPackageManager(dir) {
208
+ const lockfiles = [
209
+ { file: "bun.lockb", manager: "bun" },
210
+ { file: "pnpm-lock.yaml", manager: "pnpm" },
211
+ { file: "yarn.lock", manager: "yarn" },
212
+ { file: "package-lock.json", manager: "npm" }
213
+ ];
214
+ for (const { file, manager } of lockfiles) {
215
+ try {
216
+ await access(path3.join(dir, file), constants.R_OK);
217
+ return manager;
218
+ } catch {
219
+ }
220
+ }
221
+ try {
222
+ await access(path3.join(dir, "package.json"), constants.R_OK);
223
+ return "npm";
224
+ } catch {
225
+ return null;
226
+ }
227
+ }
228
+ function runInstall(dir, packageManager) {
229
+ return new Promise((resolve, reject) => {
230
+ const child = spawn(packageManager, ["install"], {
231
+ cwd: dir,
232
+ stdio: "inherit"
233
+ });
234
+ child.on("close", (code) => {
235
+ if (code === 0) {
236
+ resolve();
237
+ } else {
238
+ reject(new Error(`${packageManager} install failed with code ${code}`));
239
+ }
240
+ });
241
+ child.on("error", reject);
242
+ });
243
+ }
244
+
245
+ // src/ui/selector.ts
246
+ import inquirer from "inquirer";
247
+ var AI_TOOLS = [
248
+ {
249
+ name: "Claude Code",
250
+ value: "claude",
251
+ description: "Anthropic's Claude coding assistant"
252
+ },
253
+ {
254
+ name: "Codex",
255
+ value: "codex",
256
+ description: "OpenAI's Codex coding assistant"
257
+ }
258
+ ];
259
+ async function selectAITool() {
260
+ const { tool } = await inquirer.prompt([
261
+ {
262
+ type: "list",
263
+ name: "tool",
264
+ message: "Select AI coding assistant:",
265
+ choices: AI_TOOLS.map((t) => ({
266
+ name: `${t.name} - ${t.description}`,
267
+ value: t.value,
268
+ short: t.name
269
+ }))
270
+ }
271
+ ]);
272
+ return tool;
273
+ }
274
+ async function confirm(message, defaultValue = true) {
275
+ const { confirmed } = await inquirer.prompt([
276
+ {
277
+ type: "confirm",
278
+ name: "confirmed",
279
+ message,
280
+ default: defaultValue
281
+ }
282
+ ]);
283
+ return confirmed;
284
+ }
285
+ async function selectMultiple(message, choices) {
286
+ const { selected } = await inquirer.prompt([
287
+ {
288
+ type: "checkbox",
289
+ name: "selected",
290
+ message,
291
+ choices
292
+ }
293
+ ]);
294
+ return selected;
295
+ }
296
+
297
+ // src/commands/new.ts
298
+ async function newCommand(branchName, options) {
26
299
  if (!await isGitRepo()) {
27
300
  console.error(chalk.red("Error: Not a git repository"));
28
301
  process.exit(1);
29
302
  }
30
- const mainRepoPath = await getGitRoot();
31
- const worktrees = await listWorktrees();
32
- if (worktrees.length === 0) {
33
- console.log(chalk.yellow("No worktrees found"));
303
+ const mainRepoPath2 = await getGitRoot();
304
+ const repoName = path4.basename(mainRepoPath2);
305
+ const worktreePath = getWorktreePath(mainRepoPath2, branchName);
306
+ console.log(chalk.cyan(`
307
+ Creating worktree for branch: ${chalk.bold(branchName)}`));
308
+ console.log(chalk.dim(`Repository: ${repoName}`));
309
+ console.log(chalk.dim(`Worktree path: ${worktreePath}
310
+ `));
311
+ const spinner = ora("Creating worktree...").start();
312
+ try {
313
+ await createWorktree(worktreePath, branchName);
314
+ spinner.succeed(chalk.green("Worktree created successfully"));
315
+ } catch (error) {
316
+ spinner.fail(chalk.red("Failed to create worktree"));
317
+ console.error(chalk.red(error.message || error));
318
+ process.exit(1);
319
+ }
320
+ if (options.push) {
321
+ const pushSpinner = ora("Pushing branch to remote...").start();
322
+ try {
323
+ await pushBranch(branchName, worktreePath);
324
+ pushSpinner.succeed(chalk.green(`Pushed ${branchName} to origin`));
325
+ } catch (error) {
326
+ pushSpinner.fail(chalk.yellow(`Could not push: ${error.message}`));
327
+ }
328
+ }
329
+ const envSpinner = ora("Copying .env files...").start();
330
+ try {
331
+ const copiedFiles = await copyEnvFiles(mainRepoPath2, worktreePath);
332
+ if (copiedFiles.length > 0) {
333
+ envSpinner.succeed(chalk.green(`Copied ${copiedFiles.length} env file(s): ${copiedFiles.join(", ")}`));
334
+ } else {
335
+ envSpinner.info(chalk.yellow("No .env files found to copy"));
336
+ }
337
+ } catch (error) {
338
+ envSpinner.warn(chalk.yellow(`Warning: Could not copy env files: ${error.message}`));
339
+ }
340
+ if (options.install) {
341
+ const packageManager = await detectPackageManager(worktreePath);
342
+ if (packageManager) {
343
+ const installSpinner = ora(`Running ${packageManager} install...`).start();
344
+ try {
345
+ await runInstall(worktreePath, packageManager);
346
+ installSpinner.succeed(chalk.green(`${packageManager} install completed`));
347
+ } catch (error) {
348
+ installSpinner.fail(chalk.red(`${packageManager} install failed: ${error.message}`));
349
+ }
350
+ }
351
+ } else {
352
+ const packageManager = await detectPackageManager(worktreePath);
353
+ if (packageManager) {
354
+ console.log(chalk.dim(`
355
+ Tip: Run '${packageManager} install' in the worktree, or use 'wt new --install' next time`));
356
+ }
357
+ }
358
+ if (options.skipLaunch) {
359
+ console.log(chalk.green(`
360
+ \u2713 Worktree ready at: ${worktreePath}`));
361
+ console.log(chalk.dim(` cd "${worktreePath}"`));
34
362
  return;
35
363
  }
364
+ console.log("");
365
+ const selectedTool = await selectAITool();
366
+ const toolAvailable = await isToolAvailable(selectedTool);
367
+ if (!toolAvailable) {
368
+ console.error(chalk.red(`
369
+ Error: ${selectedTool} is not installed or not in PATH`));
370
+ console.log(chalk.dim(`Worktree is ready at: ${worktreePath}`));
371
+ console.log(chalk.dim(`You can manually launch your AI tool there.`));
372
+ process.exit(1);
373
+ }
36
374
  console.log(chalk.cyan(`
37
- Worktrees for: ${chalk.bold(path.basename(mainRepoPath))}
375
+ Launching ${selectedTool} in worktree...`));
376
+ launchAITool({
377
+ cwd: worktreePath,
378
+ tool: selectedTool
379
+ });
380
+ console.log(chalk.green(`
381
+ \u2713 ${selectedTool} launched in: ${worktreePath}`));
382
+ }
383
+
384
+ // src/commands/list.ts
385
+ import chalk2 from "chalk";
386
+ import path5 from "path";
387
+ async function listCommand() {
388
+ if (!await isGitRepo()) {
389
+ console.error(chalk2.red("Error: Not a git repository"));
390
+ process.exit(1);
391
+ }
392
+ const mainRepoPath2 = await getGitRoot();
393
+ const worktrees2 = await listWorktrees();
394
+ if (worktrees2.length === 0) {
395
+ console.log(chalk2.yellow("No worktrees found"));
396
+ return;
397
+ }
398
+ console.log(chalk2.cyan(`
399
+ Worktrees for: ${chalk2.bold(path5.basename(mainRepoPath2))}
38
400
  `));
39
401
  console.log(
40
- chalk.dim("\u2500".repeat(100))
402
+ chalk2.dim("\u2500".repeat(100))
41
403
  );
42
404
  console.log(
43
- chalk.bold(padEnd("Path", 50)) + chalk.bold(padEnd("Branch", 25)) + chalk.bold("Status")
405
+ chalk2.bold(padEnd("Path", 50)) + chalk2.bold(padEnd("Branch", 25)) + chalk2.bold("Status")
44
406
  );
45
407
  console.log(
46
- chalk.dim("\u2500".repeat(100))
408
+ chalk2.dim("\u2500".repeat(100))
47
409
  );
48
- for (const wt of worktrees) {
49
- const isMain = wt.path === mainRepoPath;
410
+ for (const wt of worktrees2) {
411
+ const isMain = wt.path === mainRepoPath2;
50
412
  const status = await getWorktreeStatus(wt.branch, wt.detached, isMain);
51
413
  const displayPath = shortenPath(wt.path, 48);
52
- const displayBranch = wt.detached ? chalk.yellow("(detached)") : wt.branch || "N/A";
414
+ const displayBranch = wt.detached ? chalk2.yellow("(detached)") : wt.branch || "N/A";
53
415
  console.log(
54
- padEnd(isMain ? chalk.bold(displayPath) : displayPath, 50) + padEnd(displayBranch, 25) + status
416
+ padEnd(isMain ? chalk2.bold(displayPath) : displayPath, 50) + padEnd(displayBranch, 25) + status
55
417
  );
56
418
  }
57
- console.log(chalk.dim("\u2500".repeat(100)));
58
- console.log(chalk.dim(`
59
- Total: ${worktrees.length} worktree(s)`));
419
+ console.log(chalk2.dim("\u2500".repeat(100)));
420
+ console.log(chalk2.dim(`
421
+ Total: ${worktrees2.length} worktree(s)`));
60
422
  }
61
423
  async function getWorktreeStatus(branch, detached, isMain) {
62
424
  if (isMain) {
63
- return chalk.blue("main");
425
+ return chalk2.blue("main");
64
426
  }
65
427
  if (detached) {
66
- return chalk.yellow("detached");
428
+ return chalk2.yellow("detached");
67
429
  }
68
430
  if (!branch) {
69
- return chalk.dim("unknown");
431
+ return chalk2.dim("unknown");
70
432
  }
71
433
  const existsOnRemote = await remoteBranchExists(branch);
72
434
  const isMerged = await isBranchMerged(branch);
73
435
  if (isMerged) {
74
- return chalk.green("merged") + chalk.dim(" (can clean)");
436
+ return chalk2.green("merged") + chalk2.dim(" (can clean)");
75
437
  }
76
438
  if (!existsOnRemote) {
77
- return chalk.yellow("local only");
439
+ return chalk2.yellow("local only");
78
440
  }
79
- return chalk.green("active");
441
+ return chalk2.green("active");
80
442
  }
81
443
  function padEnd(str, length) {
82
444
  const visibleLength = str.replace(/\x1B\[[0-9;]*m/g, "").length;
@@ -85,12 +447,12 @@ function padEnd(str, length) {
85
447
  }
86
448
  function shortenPath(p, maxLength) {
87
449
  if (p.length <= maxLength) return p;
88
- const parts = p.split(path.sep);
450
+ const parts = p.split(path5.sep);
89
451
  let result = parts[parts.length - 1];
90
452
  for (let i = parts.length - 2; i >= 0; i--) {
91
- const newResult = path.join(parts[i], result);
453
+ const newResult = path5.join(parts[i], result);
92
454
  if (newResult.length > maxLength - 3) {
93
- return "..." + path.sep + result;
455
+ return "..." + path5.sep + result;
94
456
  }
95
457
  result = newResult;
96
458
  }
@@ -98,23 +460,23 @@ function shortenPath(p, maxLength) {
98
460
  }
99
461
 
100
462
  // src/commands/clean.ts
101
- import chalk2 from "chalk";
102
- import ora from "ora";
103
- import path2 from "path";
463
+ import chalk3 from "chalk";
464
+ import ora2 from "ora";
465
+ import path6 from "path";
104
466
  async function cleanCommand() {
105
467
  if (!await isGitRepo()) {
106
- console.error(chalk2.red("Error: Not a git repository"));
468
+ console.error(chalk3.red("Error: Not a git repository"));
107
469
  process.exit(1);
108
470
  }
109
- const mainRepoPath = await getGitRoot();
110
- const pruneSpinner = ora("Pruning stale references...").start();
471
+ const mainRepoPath2 = await getGitRoot();
472
+ const pruneSpinner = ora2("Pruning stale references...").start();
111
473
  await pruneWorktrees();
112
474
  pruneSpinner.succeed("Pruned stale references");
113
- const worktrees = await listWorktrees();
114
- const spinner = ora("Checking worktree status...").start();
475
+ const worktrees2 = await listWorktrees();
476
+ const spinner = ora2("Checking worktree status...").start();
115
477
  const staleWorktrees = [];
116
- for (const wt of worktrees) {
117
- if (wt.path === mainRepoPath) continue;
478
+ for (const wt of worktrees2) {
479
+ if (wt.path === mainRepoPath2) continue;
118
480
  if (wt.detached || wt.bare || !wt.branch) continue;
119
481
  const merged = await isBranchMerged(wt.branch);
120
482
  if (merged) {
@@ -128,16 +490,16 @@ async function cleanCommand() {
128
490
  }
129
491
  spinner.stop();
130
492
  if (staleWorktrees.length === 0) {
131
- console.log(chalk2.green("\n\u2713 No stale worktrees found"));
493
+ console.log(chalk3.green("\n\u2713 No stale worktrees found"));
132
494
  return;
133
495
  }
134
- console.log(chalk2.yellow(`
496
+ console.log(chalk3.yellow(`
135
497
  Found ${staleWorktrees.length} potentially stale worktree(s):
136
498
  `));
137
499
  const choices = staleWorktrees.map((wt) => {
138
- const reasonText = wt.reason === "merged" ? chalk2.green("merged") : chalk2.yellow("local only");
500
+ const reasonText = wt.reason === "merged" ? chalk3.green("merged") : chalk3.yellow("local only");
139
501
  return {
140
- name: `${path2.basename(wt.path)} (${wt.branch}) - ${reasonText}`,
502
+ name: `${path6.basename(wt.path)} (${wt.branch}) - ${reasonText}`,
141
503
  value: wt,
142
504
  checked: wt.reason === "merged"
143
505
  // Pre-select merged branches
@@ -148,7 +510,7 @@ Found ${staleWorktrees.length} potentially stale worktree(s):
148
510
  choices
149
511
  );
150
512
  if (selected.length === 0) {
151
- console.log(chalk2.yellow("\nNo worktrees selected for removal"));
513
+ console.log(chalk3.yellow("\nNo worktrees selected for removal"));
152
514
  return;
153
515
  }
154
516
  const confirmed = await confirm(
@@ -156,275 +518,652 @@ Found ${staleWorktrees.length} potentially stale worktree(s):
156
518
  true
157
519
  );
158
520
  if (!confirmed) {
159
- console.log(chalk2.yellow("Cancelled"));
521
+ console.log(chalk3.yellow("Cancelled"));
160
522
  return;
161
523
  }
162
524
  console.log("");
163
525
  let removed = 0;
164
526
  let failed = 0;
165
527
  for (const wt of selected) {
166
- const removeSpinner = ora(`Removing ${path2.basename(wt.path)}...`).start();
528
+ const removeSpinner = ora2(`Removing ${path6.basename(wt.path)}...`).start();
167
529
  try {
168
530
  await removeWorktree(wt.path, false);
169
- removeSpinner.succeed(chalk2.green(`Removed ${path2.basename(wt.path)}`));
531
+ removeSpinner.succeed(chalk3.green(`Removed ${path6.basename(wt.path)}`));
170
532
  removed++;
171
533
  } catch (error) {
172
534
  try {
173
535
  await removeWorktree(wt.path, true);
174
- removeSpinner.succeed(chalk2.green(`Removed ${path2.basename(wt.path)} (forced)`));
536
+ removeSpinner.succeed(chalk3.green(`Removed ${path6.basename(wt.path)} (forced)`));
175
537
  removed++;
176
538
  } catch (forceError) {
177
- removeSpinner.fail(chalk2.red(`Failed to remove ${path2.basename(wt.path)}: ${forceError.message}`));
539
+ removeSpinner.fail(chalk3.red(`Failed to remove ${path6.basename(wt.path)}: ${forceError.message}`));
178
540
  failed++;
179
541
  }
180
542
  }
181
543
  }
182
544
  console.log("");
183
545
  if (removed > 0) {
184
- console.log(chalk2.green(`\u2713 Removed ${removed} worktree(s)`));
546
+ console.log(chalk3.green(`\u2713 Removed ${removed} worktree(s)`));
185
547
  }
186
548
  if (failed > 0) {
187
- console.log(chalk2.red(`\u2717 Failed to remove ${failed} worktree(s)`));
549
+ console.log(chalk3.red(`\u2717 Failed to remove ${failed} worktree(s)`));
188
550
  }
189
551
  }
190
552
 
191
553
  // src/commands/remove.ts
192
- import chalk3 from "chalk";
193
- import ora2 from "ora";
194
- import path3 from "path";
554
+ import chalk4 from "chalk";
555
+ import ora3 from "ora";
556
+ import path7 from "path";
195
557
  async function removeCommand(identifier, options) {
196
558
  if (!await isGitRepo()) {
197
- console.error(chalk3.red("Error: Not a git repository"));
559
+ console.error(chalk4.red("Error: Not a git repository"));
198
560
  process.exit(1);
199
561
  }
200
- const mainRepoPath = await getGitRoot();
201
- const spinner = ora2("Finding worktree...").start();
562
+ const mainRepoPath2 = await getGitRoot();
563
+ const spinner = ora3("Finding worktree...").start();
202
564
  const worktree = await findWorktree(identifier);
203
565
  if (!worktree) {
204
- spinner.fail(chalk3.red(`Worktree not found: ${identifier}`));
205
- console.log(chalk3.dim('\nTip: Run "wt list" to see available worktrees'));
566
+ spinner.fail(chalk4.red(`Worktree not found: ${identifier}`));
567
+ console.log(chalk4.dim('\nTip: Run "wt list" to see available worktrees'));
206
568
  process.exit(1);
207
569
  }
208
570
  spinner.stop();
209
- if (worktree.path === mainRepoPath) {
210
- console.error(chalk3.red("\nError: Cannot remove the main worktree"));
571
+ if (worktree.path === mainRepoPath2) {
572
+ console.error(chalk4.red("\nError: Cannot remove the main worktree"));
211
573
  process.exit(1);
212
574
  }
213
- console.log(chalk3.cyan("\nWorktree to remove:"));
214
- console.log(chalk3.dim(` Path: ${worktree.path}`));
215
- console.log(chalk3.dim(` Branch: ${worktree.branch || "(detached)"}`));
575
+ console.log(chalk4.cyan("\nWorktree to remove:"));
576
+ console.log(chalk4.dim(` Path: ${worktree.path}`));
577
+ console.log(chalk4.dim(` Branch: ${worktree.branch || "(detached)"}`));
216
578
  if (!options.force) {
217
579
  const confirmed = await confirm("\nRemove this worktree?", false);
218
580
  if (!confirmed) {
219
- console.log(chalk3.yellow("Cancelled"));
581
+ console.log(chalk4.yellow("Cancelled"));
220
582
  return;
221
583
  }
222
584
  }
223
- const removeSpinner = ora2("Removing worktree...").start();
585
+ const removeSpinner = ora3("Removing worktree...").start();
224
586
  try {
225
587
  await removeWorktree(worktree.path, false);
226
- removeSpinner.succeed(chalk3.green(`Removed worktree: ${path3.basename(worktree.path)}`));
588
+ removeSpinner.succeed(chalk4.green(`Removed worktree: ${path7.basename(worktree.path)}`));
227
589
  } catch (error) {
228
590
  if (options.force) {
229
591
  try {
230
592
  await removeWorktree(worktree.path, true);
231
- removeSpinner.succeed(chalk3.green(`Removed worktree (forced): ${path3.basename(worktree.path)}`));
593
+ removeSpinner.succeed(chalk4.green(`Removed worktree (forced): ${path7.basename(worktree.path)}`));
232
594
  } catch (forceError) {
233
- removeSpinner.fail(chalk3.red(`Failed to remove worktree: ${forceError.message}`));
595
+ removeSpinner.fail(chalk4.red(`Failed to remove worktree: ${forceError.message}`));
234
596
  process.exit(1);
235
597
  }
236
598
  } else {
237
- removeSpinner.fail(chalk3.red(`Failed to remove worktree: ${error.message}`));
238
- console.log(chalk3.dim("\nTip: Use --force to force removal"));
599
+ removeSpinner.fail(chalk4.red(`Failed to remove worktree: ${error.message}`));
600
+ console.log(chalk4.dim("\nTip: Use --force to force removal"));
239
601
  process.exit(1);
240
602
  }
241
603
  }
242
604
  }
243
605
 
244
606
  // src/commands/interactive.ts
245
- import chalk4 from "chalk";
246
- import inquirer from "inquirer";
247
- import path4 from "path";
248
- import { execFile } from "child_process";
249
- import { promisify } from "util";
250
- var execFileAsync = promisify(execFile);
251
- async function getWorktreeStatus2(branch, isMain) {
252
- if (isMain) return chalk4.blue("main");
253
- if (!branch) return chalk4.dim("detached");
254
- const merged = await isBranchMerged(branch);
255
- if (merged) return chalk4.green("merged");
256
- const onRemote = await remoteBranchExists(branch);
257
- if (!onRemote) return chalk4.yellow("local");
258
- return chalk4.green("active");
259
- }
607
+ import blessed from "blessed";
608
+ import path8 from "path";
609
+ var screen;
610
+ var worktreeList;
611
+ var statusBar;
612
+ var helpBar;
613
+ var headerBox;
614
+ var mainRepoPath;
615
+ var currentBranch;
616
+ var defaultBranch;
617
+ var worktrees = [];
618
+ var selectedIndex = 0;
260
619
  async function interactiveCommand() {
261
620
  if (!await isGitRepo()) {
262
- console.error(chalk4.red("Error: Not a git repository"));
621
+ console.error("Error: Not a git repository");
263
622
  process.exit(1);
264
623
  }
265
- const mainRepoPath = await getGitRoot();
266
- const repoName = path4.basename(mainRepoPath);
267
- while (true) {
268
- console.clear();
269
- console.log(chalk4.cyan.bold(`
270
- Worktrees: ${repoName}
271
- `));
272
- const worktrees = await listWorktrees();
273
- const choices = [];
274
- for (const wt of worktrees) {
275
- const isMain = wt.path === mainRepoPath;
276
- const status = await getWorktreeStatus2(wt.branch, isMain);
277
- const branchDisplay = wt.branch || "(detached)";
278
- const dirName = path4.basename(wt.path);
279
- choices.push({
280
- name: ` ${dirName.padEnd(35)} ${branchDisplay.padEnd(25)} ${status}`,
281
- value: wt,
282
- short: dirName
283
- });
624
+ mainRepoPath = await getGitRoot();
625
+ currentBranch = await getCurrentBranch();
626
+ defaultBranch = await getDefaultBranch();
627
+ const repoName = path8.basename(mainRepoPath);
628
+ screen = blessed.screen({
629
+ smartCSR: true,
630
+ title: `wt - ${repoName}`
631
+ });
632
+ headerBox = blessed.box({
633
+ parent: screen,
634
+ top: 0,
635
+ left: 0,
636
+ width: "100%",
637
+ height: 1,
638
+ content: ` ${repoName} (${currentBranch})`,
639
+ style: {
640
+ fg: "white",
641
+ bg: "blue",
642
+ bold: true
284
643
  }
285
- choices.push({ name: chalk4.dim("\u2500".repeat(70)), value: "quit", short: "" });
286
- choices.push({ name: chalk4.green(" + Create new worktree"), value: "new", short: "new" });
287
- choices.push({ name: chalk4.dim(" q Quit"), value: "quit", short: "quit" });
288
- const { selected } = await inquirer.prompt([
289
- {
290
- type: "list",
291
- name: "selected",
292
- message: "Select a worktree:",
293
- choices,
294
- pageSize: 15
644
+ });
645
+ worktreeList = blessed.list({
646
+ parent: screen,
647
+ top: 1,
648
+ left: 0,
649
+ width: "100%",
650
+ height: "100%-3",
651
+ keys: true,
652
+ vi: true,
653
+ mouse: true,
654
+ style: {
655
+ selected: {
656
+ bg: "blue",
657
+ fg: "white"
658
+ },
659
+ item: {
660
+ fg: "white"
295
661
  }
296
- ]);
297
- if (selected === "quit") {
298
- console.log(chalk4.dim("\nGoodbye.\n"));
299
- break;
662
+ },
663
+ scrollbar: {
664
+ ch: " ",
665
+ style: { bg: "grey" }
300
666
  }
301
- if (selected === "new") {
302
- await handleNewWorktree(mainRepoPath);
303
- continue;
667
+ });
668
+ statusBar = blessed.box({
669
+ parent: screen,
670
+ bottom: 1,
671
+ left: 0,
672
+ width: "100%",
673
+ height: 1,
674
+ content: "",
675
+ style: {
676
+ fg: "yellow",
677
+ bg: "black"
678
+ }
679
+ });
680
+ helpBar = blessed.box({
681
+ parent: screen,
682
+ bottom: 0,
683
+ left: 0,
684
+ width: "100%",
685
+ height: 1,
686
+ content: " [n]ew [d]elete [c]laude [x]codex [Enter]cd [q]uit",
687
+ style: {
688
+ fg: "black",
689
+ bg: "white"
690
+ }
691
+ });
692
+ await refreshWorktrees();
693
+ worktreeList.on("select", (_item, index) => {
694
+ selectedIndex = index;
695
+ showPath();
696
+ });
697
+ screen.key(["q", "C-c"], () => {
698
+ screen.destroy();
699
+ process.exit(0);
700
+ });
701
+ screen.key(["n"], () => {
702
+ startCreationWizard();
703
+ });
704
+ screen.key(["d"], async () => {
705
+ await deleteSelected();
706
+ });
707
+ screen.key(["c"], async () => {
708
+ await launchAI("claude");
709
+ });
710
+ screen.key(["x"], async () => {
711
+ await launchAI("codex");
712
+ });
713
+ screen.key(["enter"], () => {
714
+ const wt = worktrees[selectedIndex];
715
+ if (wt) {
716
+ screen.destroy();
717
+ console.log(`
718
+ cd "${wt.path}"
719
+ `);
720
+ process.exit(0);
304
721
  }
305
- await handleWorktreeActions(selected, mainRepoPath);
722
+ });
723
+ screen.key(["r"], async () => {
724
+ await refreshWorktrees();
725
+ });
726
+ worktreeList.focus();
727
+ screen.render();
728
+ }
729
+ async function refreshWorktrees() {
730
+ setStatus("Loading...");
731
+ worktrees = await listWorktrees();
732
+ const items = worktrees.map((wt) => {
733
+ const isMain = wt.path === mainRepoPath;
734
+ const dirName = path8.basename(wt.path);
735
+ const branch = wt.branch || "(detached)";
736
+ const status = isMain ? "[main]" : "";
737
+ return ` ${dirName.padEnd(40)} ${branch.padEnd(25)} ${status}`;
738
+ });
739
+ worktreeList.setItems(items);
740
+ worktreeList.select(selectedIndex);
741
+ showPath();
742
+ screen.render();
743
+ }
744
+ function showPath() {
745
+ const wt = worktrees[selectedIndex];
746
+ if (wt) {
747
+ setStatus(wt.path);
748
+ } else {
749
+ setStatus("");
306
750
  }
307
751
  }
308
- async function handleNewWorktree(mainRepoPath) {
309
- const { branchName } = await inquirer.prompt([
310
- {
311
- type: "input",
312
- name: "branchName",
313
- message: "Branch name:",
314
- validate: (input) => {
315
- if (!input.trim()) return "Branch name required";
316
- if (input.startsWith("-")) return "Cannot start with -";
317
- if (input.includes("..")) return "Cannot contain ..";
318
- return true;
752
+ function setStatus(msg) {
753
+ statusBar.setContent(` ${msg}`);
754
+ screen.render();
755
+ }
756
+ function startCreationWizard() {
757
+ const state = {
758
+ branchName: "",
759
+ baseBranch: "current",
760
+ copyEnv: true,
761
+ pushToRemote: false,
762
+ aiTool: "claude"
763
+ };
764
+ askBranchName(state);
765
+ }
766
+ function askBranchName(state) {
767
+ const form = blessed.box({
768
+ parent: screen,
769
+ top: "center",
770
+ left: "center",
771
+ width: 60,
772
+ height: 12,
773
+ border: { type: "line" },
774
+ style: {
775
+ fg: "white",
776
+ bg: "black",
777
+ border: { fg: "blue" }
778
+ },
779
+ label: " New Worktree "
780
+ });
781
+ blessed.text({
782
+ parent: form,
783
+ top: 1,
784
+ left: 2,
785
+ content: `Repository: ${path8.basename(mainRepoPath)}`,
786
+ style: { fg: "cyan" }
787
+ });
788
+ blessed.text({
789
+ parent: form,
790
+ top: 2,
791
+ left: 2,
792
+ content: `Current branch: ${currentBranch}`,
793
+ style: { fg: "grey" }
794
+ });
795
+ blessed.text({
796
+ parent: form,
797
+ top: 4,
798
+ left: 2,
799
+ content: "Branch name:",
800
+ style: { fg: "white" }
801
+ });
802
+ const input = blessed.textbox({
803
+ parent: form,
804
+ top: 5,
805
+ left: 2,
806
+ width: 54,
807
+ height: 1,
808
+ style: {
809
+ fg: "white",
810
+ bg: "grey"
811
+ },
812
+ inputOnFocus: true
813
+ });
814
+ blessed.text({
815
+ parent: form,
816
+ top: 7,
817
+ left: 2,
818
+ content: "[Enter] next [Esc] cancel",
819
+ style: { fg: "grey" }
820
+ });
821
+ input.focus();
822
+ screen.render();
823
+ input.on("submit", (value) => {
824
+ if (!value || !value.trim()) {
825
+ form.destroy();
826
+ screen.render();
827
+ return;
828
+ }
829
+ try {
830
+ validateBranchName(value.trim());
831
+ state.branchName = value.trim();
832
+ form.destroy();
833
+ askBaseBranch(state);
834
+ } catch (e) {
835
+ setStatus(`Error: ${e.message}`);
836
+ input.focus();
837
+ screen.render();
838
+ }
839
+ });
840
+ input.on("cancel", () => {
841
+ form.destroy();
842
+ screen.render();
843
+ });
844
+ input.readInput();
845
+ }
846
+ function askBaseBranch(state) {
847
+ const form = blessed.box({
848
+ parent: screen,
849
+ top: "center",
850
+ left: "center",
851
+ width: 50,
852
+ height: 10,
853
+ border: { type: "line" },
854
+ style: {
855
+ fg: "white",
856
+ bg: "black",
857
+ border: { fg: "blue" }
858
+ },
859
+ label: " Base Branch "
860
+ });
861
+ blessed.text({
862
+ parent: form,
863
+ top: 1,
864
+ left: 2,
865
+ content: "Create worktree from:",
866
+ style: { fg: "white" }
867
+ });
868
+ const list = blessed.list({
869
+ parent: form,
870
+ top: 3,
871
+ left: 2,
872
+ width: 44,
873
+ height: 3,
874
+ keys: true,
875
+ vi: true,
876
+ style: {
877
+ selected: { bg: "blue", fg: "white" },
878
+ item: { fg: "white" }
879
+ },
880
+ items: [
881
+ ` Current branch (${currentBranch})`,
882
+ ` Default branch (${defaultBranch})`
883
+ ]
884
+ });
885
+ blessed.text({
886
+ parent: form,
887
+ top: 7,
888
+ left: 2,
889
+ content: "[Enter] select [Esc] cancel",
890
+ style: { fg: "grey" }
891
+ });
892
+ list.focus();
893
+ screen.render();
894
+ list.on("select", (_item, index) => {
895
+ state.baseBranch = index === 0 ? "current" : "default";
896
+ form.destroy();
897
+ askCopyEnv(state);
898
+ });
899
+ list.key(["escape"], () => {
900
+ form.destroy();
901
+ screen.render();
902
+ });
903
+ }
904
+ function askCopyEnv(state) {
905
+ const form = blessed.box({
906
+ parent: screen,
907
+ top: "center",
908
+ left: "center",
909
+ width: 40,
910
+ height: 8,
911
+ border: { type: "line" },
912
+ style: {
913
+ fg: "white",
914
+ bg: "black",
915
+ border: { fg: "blue" }
916
+ },
917
+ label: " Environment Files "
918
+ });
919
+ blessed.text({
920
+ parent: form,
921
+ top: 1,
922
+ left: 2,
923
+ content: "Copy .env files to worktree?",
924
+ style: { fg: "white" }
925
+ });
926
+ const list = blessed.list({
927
+ parent: form,
928
+ top: 3,
929
+ left: 2,
930
+ width: 34,
931
+ height: 2,
932
+ keys: true,
933
+ vi: true,
934
+ style: {
935
+ selected: { bg: "blue", fg: "white" },
936
+ item: { fg: "white" }
937
+ },
938
+ items: [" Yes (recommended)", " No"]
939
+ });
940
+ list.focus();
941
+ screen.render();
942
+ list.on("select", (_item, index) => {
943
+ state.copyEnv = index === 0;
944
+ form.destroy();
945
+ askPushToRemote(state);
946
+ });
947
+ list.key(["escape"], () => {
948
+ form.destroy();
949
+ screen.render();
950
+ });
951
+ }
952
+ function askPushToRemote(state) {
953
+ const form = blessed.box({
954
+ parent: screen,
955
+ top: "center",
956
+ left: "center",
957
+ width: 45,
958
+ height: 8,
959
+ border: { type: "line" },
960
+ style: {
961
+ fg: "white",
962
+ bg: "black",
963
+ border: { fg: "blue" }
964
+ },
965
+ label: " Push to Remote "
966
+ });
967
+ blessed.text({
968
+ parent: form,
969
+ top: 1,
970
+ left: 2,
971
+ content: "Push branch to GitHub immediately?",
972
+ style: { fg: "white" }
973
+ });
974
+ const list = blessed.list({
975
+ parent: form,
976
+ top: 3,
977
+ left: 2,
978
+ width: 39,
979
+ height: 2,
980
+ keys: true,
981
+ vi: true,
982
+ style: {
983
+ selected: { bg: "blue", fg: "white" },
984
+ item: { fg: "white" }
985
+ },
986
+ items: [" No (push later)", " Yes (visible on GitHub now)"]
987
+ });
988
+ list.focus();
989
+ screen.render();
990
+ list.on("select", (_item, index) => {
991
+ state.pushToRemote = index === 1;
992
+ form.destroy();
993
+ askAITool(state);
994
+ });
995
+ list.key(["escape"], () => {
996
+ form.destroy();
997
+ screen.render();
998
+ });
999
+ }
1000
+ function askAITool(state) {
1001
+ const form = blessed.box({
1002
+ parent: screen,
1003
+ top: "center",
1004
+ left: "center",
1005
+ width: 40,
1006
+ height: 10,
1007
+ border: { type: "line" },
1008
+ style: {
1009
+ fg: "white",
1010
+ bg: "black",
1011
+ border: { fg: "blue" }
1012
+ },
1013
+ label: " Launch AI Tool "
1014
+ });
1015
+ blessed.text({
1016
+ parent: form,
1017
+ top: 1,
1018
+ left: 2,
1019
+ content: "Which AI assistant to launch?",
1020
+ style: { fg: "white" }
1021
+ });
1022
+ const list = blessed.list({
1023
+ parent: form,
1024
+ top: 3,
1025
+ left: 2,
1026
+ width: 34,
1027
+ height: 3,
1028
+ keys: true,
1029
+ vi: true,
1030
+ style: {
1031
+ selected: { bg: "blue", fg: "white" },
1032
+ item: { fg: "white" }
1033
+ },
1034
+ items: [" Claude Code", " Codex", " Skip (just create worktree)"]
1035
+ });
1036
+ list.focus();
1037
+ screen.render();
1038
+ list.on("select", (_item, index) => {
1039
+ state.aiTool = index === 0 ? "claude" : index === 1 ? "codex" : "skip";
1040
+ form.destroy();
1041
+ executeCreation(state);
1042
+ });
1043
+ list.key(["escape"], () => {
1044
+ form.destroy();
1045
+ screen.render();
1046
+ });
1047
+ }
1048
+ async function executeCreation(state) {
1049
+ const { branchName, baseBranch, copyEnv, pushToRemote, aiTool } = state;
1050
+ setStatus(`Creating ${branchName}...`);
1051
+ try {
1052
+ if (baseBranch === "default" && currentBranch !== defaultBranch) {
1053
+ const worktreePath = getWorktreePath(mainRepoPath, branchName);
1054
+ const { execFile: execFile2 } = await import("child_process");
1055
+ const { promisify: promisify2 } = await import("util");
1056
+ const execFileAsync2 = promisify2(execFile2);
1057
+ await execFileAsync2("git", ["worktree", "add", "-b", branchName, "--", worktreePath, defaultBranch]);
1058
+ if (copyEnv) {
1059
+ await copyEnvFiles(mainRepoPath, worktreePath);
1060
+ }
1061
+ if (pushToRemote) {
1062
+ setStatus(`Pushing ${branchName}...`);
1063
+ await pushBranch(branchName, worktreePath);
1064
+ }
1065
+ await refreshWorktrees();
1066
+ setStatus(`Created ${branchName}`);
1067
+ if (aiTool !== "skip") {
1068
+ await launchInWorktree(worktreePath, aiTool);
1069
+ }
1070
+ } else {
1071
+ const worktreePath = getWorktreePath(mainRepoPath, branchName);
1072
+ await createWorktree(worktreePath, branchName);
1073
+ if (copyEnv) {
1074
+ await copyEnvFiles(mainRepoPath, worktreePath);
1075
+ }
1076
+ if (pushToRemote) {
1077
+ setStatus(`Pushing ${branchName}...`);
1078
+ await pushBranch(branchName, worktreePath);
1079
+ }
1080
+ await refreshWorktrees();
1081
+ setStatus(`Created ${branchName}`);
1082
+ if (aiTool !== "skip") {
1083
+ await launchInWorktree(worktreePath, aiTool);
319
1084
  }
320
1085
  }
321
- ]);
322
- if (!branchName.trim()) return;
323
- const { newCommand: newCommand2 } = await import("./new-DHGI74HT.js");
324
- await newCommand2(branchName.trim(), { skipLaunch: false });
325
- }
326
- async function handleWorktreeActions(wt, mainRepoPath) {
327
- const isMain = wt.path === mainRepoPath;
328
- const dirName = path4.basename(wt.path);
329
- const actions = [
330
- { name: " Open in terminal (cd)", value: "cd" },
331
- { name: " Launch AI assistant", value: "launch" }
332
- ];
333
- if (!isMain) {
334
- actions.push({ name: chalk4.red(" Delete worktree"), value: "delete" });
1086
+ } catch (e) {
1087
+ setStatus(`Error: ${e.message}`);
335
1088
  }
336
- actions.push({ name: chalk4.dim(" Back"), value: "back" });
337
- console.log(chalk4.cyan(`
338
- ${dirName}`));
339
- console.log(chalk4.dim(` ${wt.path}
340
- `));
341
- const { action } = await inquirer.prompt([
342
- {
343
- type: "list",
344
- name: "action",
345
- message: "Action:",
346
- choices: actions
1089
+ }
1090
+ async function launchInWorktree(worktreePath, tool) {
1091
+ const available = await isToolAvailable(tool);
1092
+ if (!available) {
1093
+ setStatus(`${tool} is not installed`);
1094
+ return;
1095
+ }
1096
+ setStatus(`Launching ${tool}...`);
1097
+ screen.destroy();
1098
+ launchAITool({ cwd: worktreePath, tool });
1099
+ console.log(`
1100
+ ${tool} launched in: ${worktreePath}
1101
+ `);
1102
+ process.exit(0);
1103
+ }
1104
+ async function deleteSelected() {
1105
+ const wt = worktrees[selectedIndex];
1106
+ if (!wt) return;
1107
+ if (wt.path === mainRepoPath) {
1108
+ setStatus("Cannot delete main worktree");
1109
+ return;
1110
+ }
1111
+ const dirName = path8.basename(wt.path);
1112
+ const confirm2 = blessed.question({
1113
+ parent: screen,
1114
+ top: "center",
1115
+ left: "center",
1116
+ width: 40,
1117
+ height: 5,
1118
+ border: { type: "line" },
1119
+ style: {
1120
+ fg: "white",
1121
+ bg: "black",
1122
+ border: { fg: "red" }
347
1123
  }
348
- ]);
349
- switch (action) {
350
- case "cd":
351
- console.log(chalk4.green(`
352
- To open this worktree, run:`));
353
- console.log(chalk4.cyan(` cd "${wt.path}"
354
- `));
1124
+ });
1125
+ confirm2.ask(`Delete ${dirName}?`, async (err, yes) => {
1126
+ confirm2.destroy();
1127
+ if (yes) {
1128
+ setStatus(`Deleting ${dirName}...`);
355
1129
  try {
356
- await execFileAsync("pbcopy", [], { input: wt.path });
357
- console.log(chalk4.dim("(Path copied to clipboard)\n"));
1130
+ await removeWorktree(wt.path, false);
1131
+ setStatus(`Deleted ${dirName}`);
358
1132
  } catch {
359
- }
360
- await pause();
361
- break;
362
- case "launch":
363
- const tool = await selectAITool();
364
- const available = await isToolAvailable(tool);
365
- if (!available) {
366
- console.log(chalk4.red(`
367
- ${tool} is not installed or not in PATH
368
- `));
369
- await pause();
370
- break;
371
- }
372
- console.log(chalk4.cyan(`
373
- Launching ${tool} in ${dirName}...`));
374
- launchAITool({ cwd: wt.path, tool });
375
- console.log(chalk4.green(`
376
- ${tool} launched.
377
- `));
378
- await pause();
379
- break;
380
- case "delete":
381
- const { confirm: confirm2 } = await inquirer.prompt([
382
- {
383
- type: "confirm",
384
- name: "confirm",
385
- message: `Delete ${dirName}?`,
386
- default: false
387
- }
388
- ]);
389
- if (confirm2) {
390
1133
  try {
391
- await removeWorktree(wt.path, false);
392
- console.log(chalk4.green(`
393
- Deleted ${dirName}
394
- `));
395
- } catch {
396
- try {
397
- await removeWorktree(wt.path, true);
398
- console.log(chalk4.green(`
399
- Deleted ${dirName} (forced)
400
- `));
401
- } catch (e) {
402
- console.log(chalk4.red(`
403
- Failed to delete: ${e.message}
404
- `));
405
- }
1134
+ await removeWorktree(wt.path, true);
1135
+ setStatus(`Deleted ${dirName} (forced)`);
1136
+ } catch (e) {
1137
+ setStatus(`Error: ${e.message}`);
406
1138
  }
407
- await pause();
408
1139
  }
409
- break;
410
- case "back":
411
- default:
412
- break;
413
- }
414
- }
415
- async function pause() {
416
- await inquirer.prompt([
417
- {
418
- type: "input",
419
- name: "continue",
420
- message: "Press Enter to continue..."
1140
+ if (selectedIndex > 0) selectedIndex--;
1141
+ await refreshWorktrees();
1142
+ } else {
1143
+ await refreshWorktrees();
421
1144
  }
422
- ]);
1145
+ });
1146
+ }
1147
+ async function launchAI(tool) {
1148
+ const wt = worktrees[selectedIndex];
1149
+ if (!wt) return;
1150
+ const available = await isToolAvailable(tool);
1151
+ if (!available) {
1152
+ setStatus(`${tool} is not installed`);
1153
+ return;
1154
+ }
1155
+ setStatus(`Launching ${tool}...`);
1156
+ screen.destroy();
1157
+ launchAITool({ cwd: wt.path, tool });
1158
+ console.log(`
1159
+ ${tool} launched in: ${path8.basename(wt.path)}
1160
+ `);
1161
+ process.exit(0);
423
1162
  }
424
1163
 
425
1164
  // src/index.ts
426
1165
  var program = new Command();
427
- program.name("wt").description("CLI tool to streamline git worktrees with AI coding assistants").version("1.2.1").action(async () => {
1166
+ program.name("wt").description("CLI tool to streamline git worktrees with AI coding assistants").version("1.4.0").action(async () => {
428
1167
  await interactiveCommand();
429
1168
  });
430
1169
  program.command("new <branch-name>").description("Create a new worktree and launch AI assistant").option("-i, --install", "Run package manager install after creating worktree").option("-s, --skip-launch", "Create worktree without launching AI assistant").option("-p, --push", "Push branch to remote (visible on GitHub)").action(async (branchName, options) => {