worktree-launcher 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,8 +36,32 @@ npm install -g worktree-launcher
36
36
 
37
37
  Requires Node.js 18+ and git.
38
38
 
39
+ ## Interactive Mode
40
+
41
+ Run `wt` with no arguments to enter interactive mode:
42
+
43
+ ```bash
44
+ wt
45
+ ```
46
+
47
+ This shows a navigable list of all worktrees where you can:
48
+ - Select a worktree to see actions
49
+ - Launch an AI assistant in any worktree
50
+ - Delete worktrees
51
+ - Create new worktrees
52
+
53
+ Use arrow keys to navigate and Enter to select.
54
+
39
55
  ## Commands
40
56
 
57
+ ### wt
58
+
59
+ Run with no arguments for interactive mode.
60
+
61
+ ```bash
62
+ wt
63
+ ```
64
+
41
65
  ### wt new
42
66
 
43
67
  Create a new worktree and optionally launch an AI assistant.
@@ -108,15 +132,15 @@ A typical development workflow:
108
132
  # 1. Start in your main repository
109
133
  cd ~/code/myproject
110
134
 
111
- # 2. Create a worktree for a new feature
135
+ # 2. Open interactive mode to see all worktrees
136
+ wt
137
+
138
+ # 3. Or create a worktree directly
112
139
  wt new feature-user-auth
113
140
  # Select Claude Code or Codex from the prompt
114
141
  # AI assistant launches in the new worktree
115
142
 
116
- # 3. Work on the feature, commit, push, create PR
117
-
118
- # 4. Back in main repo, check your worktrees
119
- wt list
143
+ # 4. Work on the feature, commit, push, create PR
120
144
 
121
145
  # 5. After PR is merged, clean up
122
146
  wt clean
@@ -0,0 +1,373 @@
1
+ // src/commands/new.ts
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import path4 from "path";
5
+
6
+ // src/utils/git.ts
7
+ import { execFile } from "child_process";
8
+ import { promisify } from "util";
9
+ import path from "path";
10
+ var execFileAsync = promisify(execFile);
11
+ async function isGitRepo() {
12
+ try {
13
+ await execFileAsync("git", ["rev-parse", "--git-dir"]);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ async function getGitRoot() {
20
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"]);
21
+ return stdout.trim();
22
+ }
23
+ async function branchExists(branchName) {
24
+ try {
25
+ await execFileAsync("git", ["rev-parse", "--verify", branchName]);
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+ async function remoteBranchExists(branchName) {
32
+ try {
33
+ const { stdout } = await execFileAsync("git", ["branch", "-r"]);
34
+ const remoteBranches = stdout.split("\n").map((b) => b.trim());
35
+ return remoteBranches.some(
36
+ (b) => b === `origin/${branchName}` || b.endsWith(`/${branchName}`)
37
+ );
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+ async function getDefaultBranch() {
43
+ try {
44
+ const { stdout } = await execFileAsync("git", ["symbolic-ref", "refs/remotes/origin/HEAD"]);
45
+ return stdout.trim().replace("refs/remotes/origin/", "");
46
+ } catch {
47
+ if (await branchExists("main")) return "main";
48
+ if (await branchExists("master")) return "master";
49
+ return "main";
50
+ }
51
+ }
52
+ async function createWorktree(worktreePath, branchName) {
53
+ validateBranchName(branchName);
54
+ const exists = await branchExists(branchName);
55
+ if (exists) {
56
+ await execFileAsync("git", ["worktree", "add", "--", worktreePath, branchName]);
57
+ } else {
58
+ await execFileAsync("git", ["worktree", "add", "-b", branchName, "--", worktreePath]);
59
+ }
60
+ }
61
+ async function listWorktrees() {
62
+ const { stdout } = await execFileAsync("git", ["worktree", "list", "--porcelain"]);
63
+ const worktrees = [];
64
+ let current = {};
65
+ for (const line of stdout.split("\n")) {
66
+ if (line.startsWith("worktree ")) {
67
+ if (current.path) {
68
+ worktrees.push(current);
69
+ }
70
+ current = {
71
+ path: line.substring(9),
72
+ bare: false,
73
+ detached: false
74
+ };
75
+ } else if (line.startsWith("HEAD ")) {
76
+ current.head = line.substring(5);
77
+ } else if (line.startsWith("branch ")) {
78
+ current.branch = line.substring(7).replace("refs/heads/", "");
79
+ } else if (line === "bare") {
80
+ current.bare = true;
81
+ } else if (line === "detached") {
82
+ current.detached = true;
83
+ }
84
+ }
85
+ if (current.path) {
86
+ worktrees.push(current);
87
+ }
88
+ return worktrees;
89
+ }
90
+ async function removeWorktree(worktreePath, force = false) {
91
+ const args = ["worktree", "remove"];
92
+ if (force) args.push("--force");
93
+ args.push(worktreePath);
94
+ await execFileAsync("git", args);
95
+ }
96
+ async function pruneWorktrees() {
97
+ await execFileAsync("git", ["worktree", "prune"]);
98
+ }
99
+ async function isBranchMerged(branchName) {
100
+ try {
101
+ const defaultBranch = await getDefaultBranch();
102
+ const { stdout } = await execFileAsync("git", ["branch", "--merged", defaultBranch]);
103
+ const mergedBranches = stdout.split("\n").map((b) => b.trim().replace("* ", ""));
104
+ return mergedBranches.includes(branchName);
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+ function validateBranchName(branchName) {
110
+ if (!branchName || branchName.trim() === "") {
111
+ throw new Error("Branch name cannot be empty");
112
+ }
113
+ if (branchName.startsWith("-")) {
114
+ throw new Error("Branch name cannot start with -");
115
+ }
116
+ if (branchName.includes("..")) {
117
+ throw new Error("Branch name cannot contain ..");
118
+ }
119
+ if (branchName.length > 250) {
120
+ throw new Error("Branch name too long (max 250 characters)");
121
+ }
122
+ }
123
+ function getWorktreePath(mainRepoPath, branchName) {
124
+ validateBranchName(branchName);
125
+ const repoName = path.basename(mainRepoPath);
126
+ const safeBranchName = branchName.replace(/\//g, "-");
127
+ return path.join(path.dirname(mainRepoPath), `${repoName}-${safeBranchName}`);
128
+ }
129
+ async function findWorktree(identifier) {
130
+ const worktrees = await listWorktrees();
131
+ return worktrees.find(
132
+ (wt) => wt.branch === identifier || wt.path === identifier || path.basename(wt.path) === identifier || wt.path.endsWith(identifier)
133
+ );
134
+ }
135
+
136
+ // src/utils/env.ts
137
+ import { glob } from "glob";
138
+ import { copyFile } from "fs/promises";
139
+ import path2 from "path";
140
+ async function findEnvFiles(sourceDir) {
141
+ const files = await glob(".env*", {
142
+ cwd: sourceDir,
143
+ dot: true,
144
+ nodir: true
145
+ });
146
+ return files.filter((file) => {
147
+ if (file !== ".env" && !file.startsWith(".env.")) return false;
148
+ if (file.endsWith(".example") || file.endsWith(".sample") || file.endsWith(".template")) return false;
149
+ return true;
150
+ });
151
+ }
152
+ async function copyEnvFiles(sourceDir, destDir) {
153
+ const envFiles = await findEnvFiles(sourceDir);
154
+ const copied = [];
155
+ for (const file of envFiles) {
156
+ try {
157
+ await copyFile(path2.join(sourceDir, file), path2.join(destDir, file));
158
+ copied.push(file);
159
+ } catch (error) {
160
+ console.warn(`Warning: Could not copy ${file}: ${error}`);
161
+ }
162
+ }
163
+ return copied;
164
+ }
165
+
166
+ // src/utils/launcher.ts
167
+ import { spawn } from "child_process";
168
+ import { access } from "fs/promises";
169
+ import path3 from "path";
170
+ import { constants } from "fs";
171
+ function launchAITool(options) {
172
+ const { cwd, tool } = options;
173
+ spawn(tool, [], {
174
+ cwd,
175
+ stdio: "inherit",
176
+ shell: true
177
+ });
178
+ }
179
+ async function isToolAvailable(tool) {
180
+ return new Promise((resolve) => {
181
+ const child = spawn("which", [tool]);
182
+ child.on("close", (code) => {
183
+ resolve(code === 0);
184
+ });
185
+ child.on("error", () => {
186
+ resolve(false);
187
+ });
188
+ });
189
+ }
190
+ async function detectPackageManager(dir) {
191
+ const lockfiles = [
192
+ { file: "bun.lockb", manager: "bun" },
193
+ { file: "pnpm-lock.yaml", manager: "pnpm" },
194
+ { file: "yarn.lock", manager: "yarn" },
195
+ { file: "package-lock.json", manager: "npm" }
196
+ ];
197
+ for (const { file, manager } of lockfiles) {
198
+ try {
199
+ await access(path3.join(dir, file), constants.R_OK);
200
+ return manager;
201
+ } catch {
202
+ }
203
+ }
204
+ try {
205
+ await access(path3.join(dir, "package.json"), constants.R_OK);
206
+ return "npm";
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+ function runInstall(dir, packageManager) {
212
+ return new Promise((resolve, reject) => {
213
+ const child = spawn(packageManager, ["install"], {
214
+ cwd: dir,
215
+ stdio: "inherit"
216
+ });
217
+ child.on("close", (code) => {
218
+ if (code === 0) {
219
+ resolve();
220
+ } else {
221
+ reject(new Error(`${packageManager} install failed with code ${code}`));
222
+ }
223
+ });
224
+ child.on("error", reject);
225
+ });
226
+ }
227
+
228
+ // src/ui/selector.ts
229
+ import inquirer from "inquirer";
230
+ var AI_TOOLS = [
231
+ {
232
+ name: "Claude Code",
233
+ value: "claude",
234
+ description: "Anthropic's Claude coding assistant"
235
+ },
236
+ {
237
+ name: "Codex",
238
+ value: "codex",
239
+ description: "OpenAI's Codex coding assistant"
240
+ }
241
+ ];
242
+ async function selectAITool() {
243
+ const { tool } = await inquirer.prompt([
244
+ {
245
+ type: "list",
246
+ name: "tool",
247
+ message: "Select AI coding assistant:",
248
+ choices: AI_TOOLS.map((t) => ({
249
+ name: `${t.name} - ${t.description}`,
250
+ value: t.value,
251
+ short: t.name
252
+ }))
253
+ }
254
+ ]);
255
+ return tool;
256
+ }
257
+ async function confirm(message, defaultValue = true) {
258
+ const { confirmed } = await inquirer.prompt([
259
+ {
260
+ type: "confirm",
261
+ name: "confirmed",
262
+ message,
263
+ default: defaultValue
264
+ }
265
+ ]);
266
+ return confirmed;
267
+ }
268
+ async function selectMultiple(message, choices) {
269
+ const { selected } = await inquirer.prompt([
270
+ {
271
+ type: "checkbox",
272
+ name: "selected",
273
+ message,
274
+ choices
275
+ }
276
+ ]);
277
+ return selected;
278
+ }
279
+
280
+ // src/commands/new.ts
281
+ async function newCommand(branchName, options) {
282
+ if (!await isGitRepo()) {
283
+ console.error(chalk.red("Error: Not a git repository"));
284
+ process.exit(1);
285
+ }
286
+ const mainRepoPath = await getGitRoot();
287
+ const repoName = path4.basename(mainRepoPath);
288
+ const worktreePath = getWorktreePath(mainRepoPath, branchName);
289
+ console.log(chalk.cyan(`
290
+ Creating worktree for branch: ${chalk.bold(branchName)}`));
291
+ console.log(chalk.dim(`Repository: ${repoName}`));
292
+ console.log(chalk.dim(`Worktree path: ${worktreePath}
293
+ `));
294
+ const spinner = ora("Creating worktree...").start();
295
+ try {
296
+ await createWorktree(worktreePath, branchName);
297
+ spinner.succeed(chalk.green("Worktree created successfully"));
298
+ } catch (error) {
299
+ spinner.fail(chalk.red("Failed to create worktree"));
300
+ console.error(chalk.red(error.message || error));
301
+ process.exit(1);
302
+ }
303
+ const envSpinner = ora("Copying .env files...").start();
304
+ try {
305
+ const copiedFiles = await copyEnvFiles(mainRepoPath, worktreePath);
306
+ if (copiedFiles.length > 0) {
307
+ envSpinner.succeed(chalk.green(`Copied ${copiedFiles.length} env file(s): ${copiedFiles.join(", ")}`));
308
+ } else {
309
+ envSpinner.info(chalk.yellow("No .env files found to copy"));
310
+ }
311
+ } catch (error) {
312
+ envSpinner.warn(chalk.yellow(`Warning: Could not copy env files: ${error.message}`));
313
+ }
314
+ if (options.install) {
315
+ const packageManager = await detectPackageManager(worktreePath);
316
+ if (packageManager) {
317
+ const installSpinner = ora(`Running ${packageManager} install...`).start();
318
+ try {
319
+ await runInstall(worktreePath, packageManager);
320
+ installSpinner.succeed(chalk.green(`${packageManager} install completed`));
321
+ } catch (error) {
322
+ installSpinner.fail(chalk.red(`${packageManager} install failed: ${error.message}`));
323
+ }
324
+ }
325
+ } else {
326
+ const packageManager = await detectPackageManager(worktreePath);
327
+ if (packageManager) {
328
+ console.log(chalk.dim(`
329
+ Tip: Run '${packageManager} install' in the worktree, or use 'wt new --install' next time`));
330
+ }
331
+ }
332
+ if (options.skipLaunch) {
333
+ console.log(chalk.green(`
334
+ \u2713 Worktree ready at: ${worktreePath}`));
335
+ console.log(chalk.dim(` cd "${worktreePath}"`));
336
+ return;
337
+ }
338
+ console.log("");
339
+ const selectedTool = await selectAITool();
340
+ const toolAvailable = await isToolAvailable(selectedTool);
341
+ if (!toolAvailable) {
342
+ console.error(chalk.red(`
343
+ Error: ${selectedTool} is not installed or not in PATH`));
344
+ console.log(chalk.dim(`Worktree is ready at: ${worktreePath}`));
345
+ console.log(chalk.dim(`You can manually launch your AI tool there.`));
346
+ process.exit(1);
347
+ }
348
+ console.log(chalk.cyan(`
349
+ Launching ${selectedTool} in worktree...`));
350
+ launchAITool({
351
+ cwd: worktreePath,
352
+ tool: selectedTool
353
+ });
354
+ console.log(chalk.green(`
355
+ \u2713 ${selectedTool} launched in: ${worktreePath}`));
356
+ }
357
+
358
+ export {
359
+ isGitRepo,
360
+ getGitRoot,
361
+ remoteBranchExists,
362
+ listWorktrees,
363
+ removeWorktree,
364
+ pruneWorktrees,
365
+ isBranchMerged,
366
+ findWorktree,
367
+ launchAITool,
368
+ isToolAvailable,
369
+ selectAITool,
370
+ confirm,
371
+ selectMultiple,
372
+ newCommand
373
+ };
package/dist/index.js CHANGED
@@ -1,423 +1,82 @@
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-U3OPVTLP.js";
2
18
 
3
19
  // src/index.ts
4
20
  import { Command } from "commander";
5
21
 
6
- // src/commands/new.ts
22
+ // src/commands/list.ts
7
23
  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";
14
24
  import path from "path";
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 getDefaultBranch() {
48
- try {
49
- const { stdout } = await execFileAsync("git", ["symbolic-ref", "refs/remotes/origin/HEAD"]);
50
- return stdout.trim().replace("refs/remotes/origin/", "");
51
- } catch {
52
- if (await branchExists("main")) return "main";
53
- if (await branchExists("master")) return "master";
54
- return "main";
55
- }
56
- }
57
- async function createWorktree(worktreePath, branchName) {
58
- validateBranchName(branchName);
59
- const exists = await branchExists(branchName);
60
- if (exists) {
61
- await execFileAsync("git", ["worktree", "add", "--", worktreePath, branchName]);
62
- } else {
63
- await execFileAsync("git", ["worktree", "add", "-b", branchName, "--", worktreePath]);
64
- }
65
- }
66
- async function listWorktrees() {
67
- const { stdout } = await execFileAsync("git", ["worktree", "list", "--porcelain"]);
68
- const worktrees = [];
69
- let current = {};
70
- for (const line of stdout.split("\n")) {
71
- if (line.startsWith("worktree ")) {
72
- if (current.path) {
73
- worktrees.push(current);
74
- }
75
- current = {
76
- path: line.substring(9),
77
- bare: false,
78
- detached: false
79
- };
80
- } else if (line.startsWith("HEAD ")) {
81
- current.head = line.substring(5);
82
- } else if (line.startsWith("branch ")) {
83
- current.branch = line.substring(7).replace("refs/heads/", "");
84
- } else if (line === "bare") {
85
- current.bare = true;
86
- } else if (line === "detached") {
87
- current.detached = true;
88
- }
89
- }
90
- if (current.path) {
91
- worktrees.push(current);
92
- }
93
- return worktrees;
94
- }
95
- async function removeWorktree(worktreePath, force = false) {
96
- const args = ["worktree", "remove"];
97
- if (force) args.push("--force");
98
- args.push(worktreePath);
99
- await execFileAsync("git", args);
100
- }
101
- async function pruneWorktrees() {
102
- await execFileAsync("git", ["worktree", "prune"]);
103
- }
104
- async function isBranchMerged(branchName) {
105
- try {
106
- const defaultBranch = await getDefaultBranch();
107
- const { stdout } = await execFileAsync("git", ["branch", "--merged", defaultBranch]);
108
- const mergedBranches = stdout.split("\n").map((b) => b.trim().replace("* ", ""));
109
- return mergedBranches.includes(branchName);
110
- } catch {
111
- return false;
112
- }
113
- }
114
- function validateBranchName(branchName) {
115
- if (!branchName || branchName.trim() === "") {
116
- throw new Error("Branch name cannot be empty");
117
- }
118
- if (branchName.startsWith("-")) {
119
- throw new Error("Branch name cannot start with -");
120
- }
121
- if (branchName.includes("..")) {
122
- throw new Error("Branch name cannot contain ..");
123
- }
124
- if (branchName.length > 250) {
125
- throw new Error("Branch name too long (max 250 characters)");
126
- }
127
- }
128
- function getWorktreePath(mainRepoPath, branchName) {
129
- validateBranchName(branchName);
130
- const repoName = path.basename(mainRepoPath);
131
- const safeBranchName = branchName.replace(/\//g, "-");
132
- return path.join(path.dirname(mainRepoPath), `${repoName}-${safeBranchName}`);
133
- }
134
- async function findWorktree(identifier) {
135
- const worktrees = await listWorktrees();
136
- return worktrees.find(
137
- (wt) => wt.branch === identifier || wt.path === identifier || path.basename(wt.path) === identifier || wt.path.endsWith(identifier)
138
- );
139
- }
140
-
141
- // src/utils/env.ts
142
- import { glob } from "glob";
143
- import { copyFile } from "fs/promises";
144
- import path2 from "path";
145
- async function findEnvFiles(sourceDir) {
146
- const files = await glob(".env*", {
147
- cwd: sourceDir,
148
- dot: true,
149
- nodir: true
150
- });
151
- return files.filter((file) => {
152
- if (file !== ".env" && !file.startsWith(".env.")) return false;
153
- if (file.endsWith(".example") || file.endsWith(".sample") || file.endsWith(".template")) return false;
154
- return true;
155
- });
156
- }
157
- async function copyEnvFiles(sourceDir, destDir) {
158
- const envFiles = await findEnvFiles(sourceDir);
159
- const copied = [];
160
- for (const file of envFiles) {
161
- try {
162
- await copyFile(path2.join(sourceDir, file), path2.join(destDir, file));
163
- copied.push(file);
164
- } catch (error) {
165
- console.warn(`Warning: Could not copy ${file}: ${error}`);
166
- }
167
- }
168
- return copied;
169
- }
170
-
171
- // src/utils/launcher.ts
172
- import { spawn } from "child_process";
173
- import { access } from "fs/promises";
174
- import path3 from "path";
175
- import { constants } from "fs";
176
- function launchAITool(options) {
177
- const { cwd, tool } = options;
178
- spawn(tool, [], {
179
- cwd,
180
- stdio: "inherit",
181
- shell: true
182
- });
183
- }
184
- async function isToolAvailable(tool) {
185
- return new Promise((resolve) => {
186
- const child = spawn("which", [tool]);
187
- child.on("close", (code) => {
188
- resolve(code === 0);
189
- });
190
- child.on("error", () => {
191
- resolve(false);
192
- });
193
- });
194
- }
195
- async function detectPackageManager(dir) {
196
- const lockfiles = [
197
- { file: "bun.lockb", manager: "bun" },
198
- { file: "pnpm-lock.yaml", manager: "pnpm" },
199
- { file: "yarn.lock", manager: "yarn" },
200
- { file: "package-lock.json", manager: "npm" }
201
- ];
202
- for (const { file, manager } of lockfiles) {
203
- try {
204
- await access(path3.join(dir, file), constants.R_OK);
205
- return manager;
206
- } catch {
207
- }
208
- }
209
- try {
210
- await access(path3.join(dir, "package.json"), constants.R_OK);
211
- return "npm";
212
- } catch {
213
- return null;
214
- }
215
- }
216
- function runInstall(dir, packageManager) {
217
- return new Promise((resolve, reject) => {
218
- const child = spawn(packageManager, ["install"], {
219
- cwd: dir,
220
- stdio: "inherit"
221
- });
222
- child.on("close", (code) => {
223
- if (code === 0) {
224
- resolve();
225
- } else {
226
- reject(new Error(`${packageManager} install failed with code ${code}`));
227
- }
228
- });
229
- child.on("error", reject);
230
- });
231
- }
232
-
233
- // src/ui/selector.ts
234
- import inquirer from "inquirer";
235
- var AI_TOOLS = [
236
- {
237
- name: "Claude Code",
238
- value: "claude",
239
- description: "Anthropic's Claude coding assistant"
240
- },
241
- {
242
- name: "Codex",
243
- value: "codex",
244
- description: "OpenAI's Codex coding assistant"
245
- }
246
- ];
247
- async function selectAITool() {
248
- const { tool } = await inquirer.prompt([
249
- {
250
- type: "list",
251
- name: "tool",
252
- message: "Select AI coding assistant:",
253
- choices: AI_TOOLS.map((t) => ({
254
- name: `${t.name} - ${t.description}`,
255
- value: t.value,
256
- short: t.name
257
- }))
258
- }
259
- ]);
260
- return tool;
261
- }
262
- async function confirm(message, defaultValue = true) {
263
- const { confirmed } = await inquirer.prompt([
264
- {
265
- type: "confirm",
266
- name: "confirmed",
267
- message,
268
- default: defaultValue
269
- }
270
- ]);
271
- return confirmed;
272
- }
273
- async function selectMultiple(message, choices) {
274
- const { selected } = await inquirer.prompt([
275
- {
276
- type: "checkbox",
277
- name: "selected",
278
- message,
279
- choices
280
- }
281
- ]);
282
- return selected;
283
- }
284
-
285
- // src/commands/new.ts
286
- async function newCommand(branchName, options) {
287
- if (!await isGitRepo()) {
288
- console.error(chalk.red("Error: Not a git repository"));
289
- process.exit(1);
290
- }
291
- const mainRepoPath = await getGitRoot();
292
- const repoName = path4.basename(mainRepoPath);
293
- const worktreePath = getWorktreePath(mainRepoPath, branchName);
294
- console.log(chalk.cyan(`
295
- Creating worktree for branch: ${chalk.bold(branchName)}`));
296
- console.log(chalk.dim(`Repository: ${repoName}`));
297
- console.log(chalk.dim(`Worktree path: ${worktreePath}
298
- `));
299
- const spinner = ora("Creating worktree...").start();
300
- try {
301
- await createWorktree(worktreePath, branchName);
302
- spinner.succeed(chalk.green("Worktree created successfully"));
303
- } catch (error) {
304
- spinner.fail(chalk.red("Failed to create worktree"));
305
- console.error(chalk.red(error.message || error));
306
- process.exit(1);
307
- }
308
- const envSpinner = ora("Copying .env files...").start();
309
- try {
310
- const copiedFiles = await copyEnvFiles(mainRepoPath, worktreePath);
311
- if (copiedFiles.length > 0) {
312
- envSpinner.succeed(chalk.green(`Copied ${copiedFiles.length} env file(s): ${copiedFiles.join(", ")}`));
313
- } else {
314
- envSpinner.info(chalk.yellow("No .env files found to copy"));
315
- }
316
- } catch (error) {
317
- envSpinner.warn(chalk.yellow(`Warning: Could not copy env files: ${error.message}`));
318
- }
319
- if (options.install) {
320
- const packageManager = await detectPackageManager(worktreePath);
321
- if (packageManager) {
322
- const installSpinner = ora(`Running ${packageManager} install...`).start();
323
- try {
324
- await runInstall(worktreePath, packageManager);
325
- installSpinner.succeed(chalk.green(`${packageManager} install completed`));
326
- } catch (error) {
327
- installSpinner.fail(chalk.red(`${packageManager} install failed: ${error.message}`));
328
- }
329
- }
330
- } else {
331
- const packageManager = await detectPackageManager(worktreePath);
332
- if (packageManager) {
333
- console.log(chalk.dim(`
334
- Tip: Run '${packageManager} install' in the worktree, or use 'wt new --install' next time`));
335
- }
336
- }
337
- if (options.skipLaunch) {
338
- console.log(chalk.green(`
339
- \u2713 Worktree ready at: ${worktreePath}`));
340
- console.log(chalk.dim(` cd "${worktreePath}"`));
341
- return;
342
- }
343
- console.log("");
344
- const selectedTool = await selectAITool();
345
- const toolAvailable = await isToolAvailable(selectedTool);
346
- if (!toolAvailable) {
347
- console.error(chalk.red(`
348
- Error: ${selectedTool} is not installed or not in PATH`));
349
- console.log(chalk.dim(`Worktree is ready at: ${worktreePath}`));
350
- console.log(chalk.dim(`You can manually launch your AI tool there.`));
351
- process.exit(1);
352
- }
353
- console.log(chalk.cyan(`
354
- Launching ${selectedTool} in worktree...`));
355
- launchAITool({
356
- cwd: worktreePath,
357
- tool: selectedTool
358
- });
359
- console.log(chalk.green(`
360
- \u2713 ${selectedTool} launched in: ${worktreePath}`));
361
- }
362
-
363
- // src/commands/list.ts
364
- import chalk2 from "chalk";
365
- import path5 from "path";
366
25
  async function listCommand() {
367
26
  if (!await isGitRepo()) {
368
- console.error(chalk2.red("Error: Not a git repository"));
27
+ console.error(chalk.red("Error: Not a git repository"));
369
28
  process.exit(1);
370
29
  }
371
30
  const mainRepoPath = await getGitRoot();
372
31
  const worktrees = await listWorktrees();
373
32
  if (worktrees.length === 0) {
374
- console.log(chalk2.yellow("No worktrees found"));
33
+ console.log(chalk.yellow("No worktrees found"));
375
34
  return;
376
35
  }
377
- console.log(chalk2.cyan(`
378
- Worktrees for: ${chalk2.bold(path5.basename(mainRepoPath))}
36
+ console.log(chalk.cyan(`
37
+ Worktrees for: ${chalk.bold(path.basename(mainRepoPath))}
379
38
  `));
380
39
  console.log(
381
- chalk2.dim("\u2500".repeat(100))
40
+ chalk.dim("\u2500".repeat(100))
382
41
  );
383
42
  console.log(
384
- chalk2.bold(padEnd("Path", 50)) + chalk2.bold(padEnd("Branch", 25)) + chalk2.bold("Status")
43
+ chalk.bold(padEnd("Path", 50)) + chalk.bold(padEnd("Branch", 25)) + chalk.bold("Status")
385
44
  );
386
45
  console.log(
387
- chalk2.dim("\u2500".repeat(100))
46
+ chalk.dim("\u2500".repeat(100))
388
47
  );
389
48
  for (const wt of worktrees) {
390
49
  const isMain = wt.path === mainRepoPath;
391
50
  const status = await getWorktreeStatus(wt.branch, wt.detached, isMain);
392
51
  const displayPath = shortenPath(wt.path, 48);
393
- const displayBranch = wt.detached ? chalk2.yellow("(detached)") : wt.branch || "N/A";
52
+ const displayBranch = wt.detached ? chalk.yellow("(detached)") : wt.branch || "N/A";
394
53
  console.log(
395
- padEnd(isMain ? chalk2.bold(displayPath) : displayPath, 50) + padEnd(displayBranch, 25) + status
54
+ padEnd(isMain ? chalk.bold(displayPath) : displayPath, 50) + padEnd(displayBranch, 25) + status
396
55
  );
397
56
  }
398
- console.log(chalk2.dim("\u2500".repeat(100)));
399
- console.log(chalk2.dim(`
57
+ console.log(chalk.dim("\u2500".repeat(100)));
58
+ console.log(chalk.dim(`
400
59
  Total: ${worktrees.length} worktree(s)`));
401
60
  }
402
61
  async function getWorktreeStatus(branch, detached, isMain) {
403
62
  if (isMain) {
404
- return chalk2.blue("main");
63
+ return chalk.blue("main");
405
64
  }
406
65
  if (detached) {
407
- return chalk2.yellow("detached");
66
+ return chalk.yellow("detached");
408
67
  }
409
68
  if (!branch) {
410
- return chalk2.dim("unknown");
69
+ return chalk.dim("unknown");
411
70
  }
412
71
  const existsOnRemote = await remoteBranchExists(branch);
413
72
  const isMerged = await isBranchMerged(branch);
414
73
  if (isMerged) {
415
- return chalk2.green("merged") + chalk2.dim(" (can clean)");
74
+ return chalk.green("merged") + chalk.dim(" (can clean)");
416
75
  }
417
76
  if (!existsOnRemote) {
418
- return chalk2.yellow("local only");
77
+ return chalk.yellow("local only");
419
78
  }
420
- return chalk2.green("active");
79
+ return chalk.green("active");
421
80
  }
422
81
  function padEnd(str, length) {
423
82
  const visibleLength = str.replace(/\x1B\[[0-9;]*m/g, "").length;
@@ -426,12 +85,12 @@ function padEnd(str, length) {
426
85
  }
427
86
  function shortenPath(p, maxLength) {
428
87
  if (p.length <= maxLength) return p;
429
- const parts = p.split(path5.sep);
88
+ const parts = p.split(path.sep);
430
89
  let result = parts[parts.length - 1];
431
90
  for (let i = parts.length - 2; i >= 0; i--) {
432
- const newResult = path5.join(parts[i], result);
91
+ const newResult = path.join(parts[i], result);
433
92
  if (newResult.length > maxLength - 3) {
434
- return "..." + path5.sep + result;
93
+ return "..." + path.sep + result;
435
94
  }
436
95
  result = newResult;
437
96
  }
@@ -439,20 +98,20 @@ function shortenPath(p, maxLength) {
439
98
  }
440
99
 
441
100
  // src/commands/clean.ts
442
- import chalk3 from "chalk";
443
- import ora2 from "ora";
444
- import path6 from "path";
101
+ import chalk2 from "chalk";
102
+ import ora from "ora";
103
+ import path2 from "path";
445
104
  async function cleanCommand() {
446
105
  if (!await isGitRepo()) {
447
- console.error(chalk3.red("Error: Not a git repository"));
106
+ console.error(chalk2.red("Error: Not a git repository"));
448
107
  process.exit(1);
449
108
  }
450
109
  const mainRepoPath = await getGitRoot();
451
- const pruneSpinner = ora2("Pruning stale references...").start();
110
+ const pruneSpinner = ora("Pruning stale references...").start();
452
111
  await pruneWorktrees();
453
112
  pruneSpinner.succeed("Pruned stale references");
454
113
  const worktrees = await listWorktrees();
455
- const spinner = ora2("Checking worktree status...").start();
114
+ const spinner = ora("Checking worktree status...").start();
456
115
  const staleWorktrees = [];
457
116
  for (const wt of worktrees) {
458
117
  if (wt.path === mainRepoPath) continue;
@@ -469,16 +128,16 @@ async function cleanCommand() {
469
128
  }
470
129
  spinner.stop();
471
130
  if (staleWorktrees.length === 0) {
472
- console.log(chalk3.green("\n\u2713 No stale worktrees found"));
131
+ console.log(chalk2.green("\n\u2713 No stale worktrees found"));
473
132
  return;
474
133
  }
475
- console.log(chalk3.yellow(`
134
+ console.log(chalk2.yellow(`
476
135
  Found ${staleWorktrees.length} potentially stale worktree(s):
477
136
  `));
478
137
  const choices = staleWorktrees.map((wt) => {
479
- const reasonText = wt.reason === "merged" ? chalk3.green("merged") : chalk3.yellow("local only");
138
+ const reasonText = wt.reason === "merged" ? chalk2.green("merged") : chalk2.yellow("local only");
480
139
  return {
481
- name: `${path6.basename(wt.path)} (${wt.branch}) - ${reasonText}`,
140
+ name: `${path2.basename(wt.path)} (${wt.branch}) - ${reasonText}`,
482
141
  value: wt,
483
142
  checked: wt.reason === "merged"
484
143
  // Pre-select merged branches
@@ -489,7 +148,7 @@ Found ${staleWorktrees.length} potentially stale worktree(s):
489
148
  choices
490
149
  );
491
150
  if (selected.length === 0) {
492
- console.log(chalk3.yellow("\nNo worktrees selected for removal"));
151
+ console.log(chalk2.yellow("\nNo worktrees selected for removal"));
493
152
  return;
494
153
  }
495
154
  const confirmed = await confirm(
@@ -497,94 +156,277 @@ Found ${staleWorktrees.length} potentially stale worktree(s):
497
156
  true
498
157
  );
499
158
  if (!confirmed) {
500
- console.log(chalk3.yellow("Cancelled"));
159
+ console.log(chalk2.yellow("Cancelled"));
501
160
  return;
502
161
  }
503
162
  console.log("");
504
163
  let removed = 0;
505
164
  let failed = 0;
506
165
  for (const wt of selected) {
507
- const removeSpinner = ora2(`Removing ${path6.basename(wt.path)}...`).start();
166
+ const removeSpinner = ora(`Removing ${path2.basename(wt.path)}...`).start();
508
167
  try {
509
168
  await removeWorktree(wt.path, false);
510
- removeSpinner.succeed(chalk3.green(`Removed ${path6.basename(wt.path)}`));
169
+ removeSpinner.succeed(chalk2.green(`Removed ${path2.basename(wt.path)}`));
511
170
  removed++;
512
171
  } catch (error) {
513
172
  try {
514
173
  await removeWorktree(wt.path, true);
515
- removeSpinner.succeed(chalk3.green(`Removed ${path6.basename(wt.path)} (forced)`));
174
+ removeSpinner.succeed(chalk2.green(`Removed ${path2.basename(wt.path)} (forced)`));
516
175
  removed++;
517
176
  } catch (forceError) {
518
- removeSpinner.fail(chalk3.red(`Failed to remove ${path6.basename(wt.path)}: ${forceError.message}`));
177
+ removeSpinner.fail(chalk2.red(`Failed to remove ${path2.basename(wt.path)}: ${forceError.message}`));
519
178
  failed++;
520
179
  }
521
180
  }
522
181
  }
523
182
  console.log("");
524
183
  if (removed > 0) {
525
- console.log(chalk3.green(`\u2713 Removed ${removed} worktree(s)`));
184
+ console.log(chalk2.green(`\u2713 Removed ${removed} worktree(s)`));
526
185
  }
527
186
  if (failed > 0) {
528
- console.log(chalk3.red(`\u2717 Failed to remove ${failed} worktree(s)`));
187
+ console.log(chalk2.red(`\u2717 Failed to remove ${failed} worktree(s)`));
529
188
  }
530
189
  }
531
190
 
532
191
  // src/commands/remove.ts
533
- import chalk4 from "chalk";
534
- import ora3 from "ora";
535
- import path7 from "path";
192
+ import chalk3 from "chalk";
193
+ import ora2 from "ora";
194
+ import path3 from "path";
536
195
  async function removeCommand(identifier, options) {
537
196
  if (!await isGitRepo()) {
538
- console.error(chalk4.red("Error: Not a git repository"));
197
+ console.error(chalk3.red("Error: Not a git repository"));
539
198
  process.exit(1);
540
199
  }
541
200
  const mainRepoPath = await getGitRoot();
542
- const spinner = ora3("Finding worktree...").start();
201
+ const spinner = ora2("Finding worktree...").start();
543
202
  const worktree = await findWorktree(identifier);
544
203
  if (!worktree) {
545
- spinner.fail(chalk4.red(`Worktree not found: ${identifier}`));
546
- console.log(chalk4.dim('\nTip: Run "wt list" to see available worktrees'));
204
+ spinner.fail(chalk3.red(`Worktree not found: ${identifier}`));
205
+ console.log(chalk3.dim('\nTip: Run "wt list" to see available worktrees'));
547
206
  process.exit(1);
548
207
  }
549
208
  spinner.stop();
550
209
  if (worktree.path === mainRepoPath) {
551
- console.error(chalk4.red("\nError: Cannot remove the main worktree"));
210
+ console.error(chalk3.red("\nError: Cannot remove the main worktree"));
552
211
  process.exit(1);
553
212
  }
554
- console.log(chalk4.cyan("\nWorktree to remove:"));
555
- console.log(chalk4.dim(` Path: ${worktree.path}`));
556
- console.log(chalk4.dim(` Branch: ${worktree.branch || "(detached)"}`));
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)"}`));
557
216
  if (!options.force) {
558
217
  const confirmed = await confirm("\nRemove this worktree?", false);
559
218
  if (!confirmed) {
560
- console.log(chalk4.yellow("Cancelled"));
219
+ console.log(chalk3.yellow("Cancelled"));
561
220
  return;
562
221
  }
563
222
  }
564
- const removeSpinner = ora3("Removing worktree...").start();
223
+ const removeSpinner = ora2("Removing worktree...").start();
565
224
  try {
566
225
  await removeWorktree(worktree.path, false);
567
- removeSpinner.succeed(chalk4.green(`Removed worktree: ${path7.basename(worktree.path)}`));
226
+ removeSpinner.succeed(chalk3.green(`Removed worktree: ${path3.basename(worktree.path)}`));
568
227
  } catch (error) {
569
228
  if (options.force) {
570
229
  try {
571
230
  await removeWorktree(worktree.path, true);
572
- removeSpinner.succeed(chalk4.green(`Removed worktree (forced): ${path7.basename(worktree.path)}`));
231
+ removeSpinner.succeed(chalk3.green(`Removed worktree (forced): ${path3.basename(worktree.path)}`));
573
232
  } catch (forceError) {
574
- removeSpinner.fail(chalk4.red(`Failed to remove worktree: ${forceError.message}`));
233
+ removeSpinner.fail(chalk3.red(`Failed to remove worktree: ${forceError.message}`));
575
234
  process.exit(1);
576
235
  }
577
236
  } else {
578
- removeSpinner.fail(chalk4.red(`Failed to remove worktree: ${error.message}`));
579
- console.log(chalk4.dim("\nTip: Use --force to force removal"));
237
+ removeSpinner.fail(chalk3.red(`Failed to remove worktree: ${error.message}`));
238
+ console.log(chalk3.dim("\nTip: Use --force to force removal"));
580
239
  process.exit(1);
581
240
  }
582
241
  }
583
242
  }
584
243
 
244
+ // 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
+ }
260
+ async function interactiveCommand() {
261
+ if (!await isGitRepo()) {
262
+ console.error(chalk4.red("Error: Not a git repository"));
263
+ process.exit(1);
264
+ }
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
+ });
284
+ }
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
295
+ }
296
+ ]);
297
+ if (selected === "quit") {
298
+ console.log(chalk4.dim("\nGoodbye.\n"));
299
+ break;
300
+ }
301
+ if (selected === "new") {
302
+ await handleNewWorktree(mainRepoPath);
303
+ continue;
304
+ }
305
+ await handleWorktreeActions(selected, mainRepoPath);
306
+ }
307
+ }
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;
319
+ }
320
+ }
321
+ ]);
322
+ if (!branchName.trim()) return;
323
+ const { newCommand: newCommand2 } = await import("./new-KE3RQFLK.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" });
335
+ }
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
347
+ }
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
+ `));
355
+ try {
356
+ await execFileAsync("pbcopy", [], { input: wt.path });
357
+ console.log(chalk4.dim("(Path copied to clipboard)\n"));
358
+ } 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
+ 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
+ }
406
+ }
407
+ await pause();
408
+ }
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..."
421
+ }
422
+ ]);
423
+ }
424
+
585
425
  // src/index.ts
586
426
  var program = new Command();
587
- program.name("wt").description("CLI tool to streamline git worktrees with AI coding assistants").version("1.0.0");
427
+ program.name("wt").description("CLI tool to streamline git worktrees with AI coding assistants").version("1.1.0").action(async () => {
428
+ await interactiveCommand();
429
+ });
588
430
  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").action(async (branchName, options) => {
589
431
  await newCommand(branchName, {
590
432
  install: options.install,
@@ -0,0 +1,6 @@
1
+ import {
2
+ newCommand
3
+ } from "./chunk-U3OPVTLP.js";
4
+ export {
5
+ newCommand
6
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worktree-launcher",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "CLI tool for managing git worktrees with AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",