@aku11i/phantom 0.1.2 → 0.3.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/phantom.js CHANGED
@@ -3,56 +3,68 @@
3
3
  // src/bin/phantom.ts
4
4
  import { argv, exit as exit6 } from "node:process";
5
5
 
6
- // src/commands/exec.ts
7
- import { spawn } from "node:child_process";
8
- import { exit as exit2 } from "node:process";
9
-
10
- // src/gardens/commands/where.ts
11
- import { access } from "node:fs/promises";
12
- import { join } from "node:path";
13
- import { exit } from "node:process";
6
+ // src/commands/create.ts
7
+ import { access as access2, mkdir } from "node:fs/promises";
8
+ import { join as join2 } from "node:path";
9
+ import { exit as exit3 } from "node:process";
14
10
 
15
- // src/git/libs/get-git-root.ts
11
+ // src/git/libs/add-worktree.ts
16
12
  import { exec } from "node:child_process";
17
13
  import { promisify } from "node:util";
18
14
  var execAsync = promisify(exec);
15
+ async function addWorktree(options) {
16
+ const { path, branch, commitish = "HEAD" } = options;
17
+ await execAsync(`git worktree add "${path}" -b "${branch}" ${commitish}`);
18
+ }
19
+
20
+ // src/git/libs/get-git-root.ts
21
+ import { exec as exec2 } from "node:child_process";
22
+ import { promisify as promisify2 } from "node:util";
23
+ var execAsync2 = promisify2(exec2);
19
24
  async function getGitRoot() {
20
- const { stdout } = await execAsync("git rev-parse --show-toplevel");
25
+ const { stdout } = await execAsync2("git rev-parse --show-toplevel");
21
26
  return stdout.trim();
22
27
  }
23
28
 
24
- // src/gardens/commands/where.ts
25
- async function whereGarden(name) {
29
+ // src/commands/shell.ts
30
+ import { spawn } from "node:child_process";
31
+ import { exit as exit2 } from "node:process";
32
+
33
+ // src/commands/where.ts
34
+ import { access } from "node:fs/promises";
35
+ import { join } from "node:path";
36
+ import { exit } from "node:process";
37
+ async function whereWorktree(name) {
26
38
  if (!name) {
27
- return { success: false, message: "Error: garden name required" };
39
+ return { success: false, message: "Error: worktree name required" };
28
40
  }
29
41
  try {
30
42
  const gitRoot = await getGitRoot();
31
- const gardensPath = join(gitRoot, ".git", "phantom", "gardens");
32
- const gardenPath = join(gardensPath, name);
43
+ const worktreesPath = join(gitRoot, ".git", "phantom", "worktrees");
44
+ const worktreePath = join(worktreesPath, name);
33
45
  try {
34
- await access(gardenPath);
46
+ await access(worktreePath);
35
47
  } catch {
36
48
  return {
37
49
  success: false,
38
- message: `Error: Garden '${name}' does not exist`
50
+ message: `Error: Worktree '${name}' does not exist`
39
51
  };
40
52
  }
41
53
  return {
42
54
  success: true,
43
- path: gardenPath
55
+ path: worktreePath
44
56
  };
45
57
  } catch (error) {
46
58
  const errorMessage = error instanceof Error ? error.message : String(error);
47
59
  return {
48
60
  success: false,
49
- message: `Error locating garden: ${errorMessage}`
61
+ message: `Error locating worktree: ${errorMessage}`
50
62
  };
51
63
  }
52
64
  }
53
- async function gardensWhereHandler(args2) {
65
+ async function whereHandler(args2) {
54
66
  const name = args2[0];
55
- const result = await whereGarden(name);
67
+ const result = await whereWorktree(name);
56
68
  if (!result.success) {
57
69
  console.error(result.message);
58
70
  exit(1);
@@ -60,87 +72,26 @@ async function gardensWhereHandler(args2) {
60
72
  console.log(result.path);
61
73
  }
62
74
 
63
- // src/commands/exec.ts
64
- async function execInGarden(gardenName, command2) {
65
- if (!gardenName) {
66
- return { success: false, message: "Error: garden name required" };
67
- }
68
- if (!command2 || command2.length === 0) {
69
- return { success: false, message: "Error: command required" };
70
- }
71
- const gardenResult = await whereGarden(gardenName);
72
- if (!gardenResult.success) {
73
- return { success: false, message: gardenResult.message };
74
- }
75
- const gardenPath = gardenResult.path;
76
- const [cmd, ...args2] = command2;
77
- return new Promise((resolve) => {
78
- const childProcess = spawn(cmd, args2, {
79
- cwd: gardenPath,
80
- stdio: "inherit"
81
- });
82
- childProcess.on("error", (error) => {
83
- resolve({
84
- success: false,
85
- message: `Error executing command: ${error.message}`
86
- });
87
- });
88
- childProcess.on("exit", (code, signal) => {
89
- if (signal) {
90
- resolve({
91
- success: false,
92
- message: `Command terminated by signal: ${signal}`,
93
- exitCode: 128 + (signal === "SIGTERM" ? 15 : 1)
94
- });
95
- } else {
96
- const exitCode = code ?? 0;
97
- resolve({
98
- success: exitCode === 0,
99
- exitCode
100
- });
101
- }
102
- });
103
- });
104
- }
105
- async function execHandler(args2) {
106
- if (args2.length < 2) {
107
- console.error("Usage: phantom exec <garden-name> <command> [args...]");
108
- exit2(1);
109
- }
110
- const gardenName = args2[0];
111
- const command2 = args2.slice(1);
112
- const result = await execInGarden(gardenName, command2);
113
- if (!result.success) {
114
- if (result.message) {
115
- console.error(result.message);
116
- }
117
- exit2(result.exitCode ?? 1);
118
- }
119
- exit2(result.exitCode ?? 0);
120
- }
121
-
122
75
  // src/commands/shell.ts
123
- import { spawn as spawn2 } from "node:child_process";
124
- import { exit as exit3 } from "node:process";
125
- async function shellInGarden(gardenName) {
126
- if (!gardenName) {
127
- return { success: false, message: "Error: garden name required" };
76
+ async function shellInWorktree(worktreeName) {
77
+ if (!worktreeName) {
78
+ return { success: false, message: "Error: worktree name required" };
128
79
  }
129
- const gardenResult = await whereGarden(gardenName);
130
- if (!gardenResult.success) {
131
- return { success: false, message: gardenResult.message };
80
+ const worktreeResult = await whereWorktree(worktreeName);
81
+ if (!worktreeResult.success) {
82
+ return { success: false, message: worktreeResult.message };
132
83
  }
133
- const gardenPath = gardenResult.path;
84
+ const worktreePath = worktreeResult.path;
134
85
  const shell = process.env.SHELL || "/bin/sh";
135
86
  return new Promise((resolve) => {
136
- const childProcess = spawn2(shell, [], {
137
- cwd: gardenPath,
87
+ const childProcess = spawn(shell, [], {
88
+ cwd: worktreePath,
138
89
  stdio: "inherit",
139
90
  env: {
140
91
  ...process.env,
141
- // Add environment variable to indicate we're in a phantom garden
142
- PHANTOM_GARDEN: gardenName,
143
- PHANTOM_GARDEN_PATH: gardenPath
92
+ // Add environment variable to indicate we're in a worktree
93
+ WORKTREE_NAME: worktreeName,
94
+ WORKTREE_PATH: worktreePath
144
95
  }
145
96
  });
146
97
  childProcess.on("error", (error) => {
@@ -168,120 +119,120 @@ async function shellInGarden(gardenName) {
168
119
  }
169
120
  async function shellHandler(args2) {
170
121
  if (args2.length < 1) {
171
- console.error("Usage: phantom shell <garden-name>");
172
- exit3(1);
122
+ console.error("Usage: phantom shell <worktree-name>");
123
+ exit2(1);
173
124
  }
174
- const gardenName = args2[0];
175
- const gardenResult = await whereGarden(gardenName);
176
- if (!gardenResult.success) {
177
- console.error(gardenResult.message);
178
- exit3(1);
125
+ const worktreeName = args2[0];
126
+ const worktreeResult = await whereWorktree(worktreeName);
127
+ if (!worktreeResult.success) {
128
+ console.error(worktreeResult.message);
129
+ exit2(1);
179
130
  }
180
- console.log(`Entering garden '${gardenName}' at ${gardenResult.path}`);
131
+ console.log(`Entering worktree '${worktreeName}' at ${worktreeResult.path}`);
181
132
  console.log("Type 'exit' to return to your original directory\n");
182
- const result = await shellInGarden(gardenName);
133
+ const result = await shellInWorktree(worktreeName);
183
134
  if (!result.success) {
184
135
  if (result.message) {
185
136
  console.error(result.message);
186
137
  }
187
- exit3(result.exitCode ?? 1);
138
+ exit2(result.exitCode ?? 1);
188
139
  }
189
- exit3(result.exitCode ?? 0);
190
- }
191
-
192
- // src/gardens/commands/create.ts
193
- import { access as access2, mkdir } from "node:fs/promises";
194
- import { join as join2 } from "node:path";
195
- import { exit as exit4 } from "node:process";
196
-
197
- // src/git/libs/add-worktree.ts
198
- import { exec as exec2 } from "node:child_process";
199
- import { promisify as promisify2 } from "node:util";
200
- var execAsync2 = promisify2(exec2);
201
- async function addWorktree(options) {
202
- const { path, branch, commitish = "HEAD" } = options;
203
- await execAsync2(`git worktree add "${path}" -b "${branch}" ${commitish}`);
140
+ exit2(result.exitCode ?? 0);
204
141
  }
205
142
 
206
- // src/gardens/commands/create.ts
207
- async function createGarden(name) {
143
+ // src/commands/create.ts
144
+ async function createWorktree(name) {
208
145
  if (!name) {
209
- return { success: false, message: "Error: garden name required" };
146
+ return { success: false, message: "Error: worktree name required" };
210
147
  }
211
148
  try {
212
149
  const gitRoot = await getGitRoot();
213
- const gardensPath = join2(gitRoot, ".git", "phantom", "gardens");
214
- const worktreePath = join2(gardensPath, name);
150
+ const worktreesPath = join2(gitRoot, ".git", "phantom", "worktrees");
151
+ const worktreePath = join2(worktreesPath, name);
215
152
  try {
216
- await access2(gardensPath);
153
+ await access2(worktreesPath);
217
154
  } catch {
218
- await mkdir(gardensPath, { recursive: true });
155
+ await mkdir(worktreesPath, { recursive: true });
219
156
  }
220
157
  try {
221
158
  await access2(worktreePath);
222
159
  return {
223
160
  success: false,
224
- message: `Error: garden '${name}' already exists`
161
+ message: `Error: worktree '${name}' already exists`
225
162
  };
226
163
  } catch {
227
164
  }
228
165
  await addWorktree({
229
166
  path: worktreePath,
230
- branch: `phantom/gardens/${name}`,
167
+ branch: name,
231
168
  commitish: "HEAD"
232
169
  });
233
170
  return {
234
171
  success: true,
235
- message: `Created garden '${name}' at ${worktreePath}`,
172
+ message: `Created worktree '${name}' at ${worktreePath}`,
236
173
  path: worktreePath
237
174
  };
238
175
  } catch (error) {
239
176
  const errorMessage = error instanceof Error ? error.message : String(error);
240
177
  return {
241
178
  success: false,
242
- message: `Error creating garden: ${errorMessage}`
179
+ message: `Error creating worktree: ${errorMessage}`
243
180
  };
244
181
  }
245
182
  }
246
- async function gardensCreateHandler(args2) {
183
+ async function createHandler(args2) {
247
184
  const name = args2[0];
248
- const result = await createGarden(name);
185
+ const openShell = args2.includes("--shell");
186
+ const result = await createWorktree(name);
249
187
  if (!result.success) {
250
188
  console.error(result.message);
251
- exit4(1);
189
+ exit3(1);
252
190
  }
253
191
  console.log(result.message);
192
+ if (openShell && result.path) {
193
+ console.log(`
194
+ Entering worktree '${name}' at ${result.path}`);
195
+ console.log("Type 'exit' to return to your original directory\n");
196
+ const shellResult = await shellInWorktree(name);
197
+ if (!shellResult.success) {
198
+ if (shellResult.message) {
199
+ console.error(shellResult.message);
200
+ }
201
+ exit3(shellResult.exitCode ?? 1);
202
+ }
203
+ exit3(shellResult.exitCode ?? 0);
204
+ }
254
205
  }
255
206
 
256
- // src/gardens/commands/delete.ts
207
+ // src/commands/delete.ts
257
208
  import { exec as exec3 } from "node:child_process";
258
209
  import { access as access3 } from "node:fs/promises";
259
210
  import { join as join3 } from "node:path";
260
- import { exit as exit5 } from "node:process";
211
+ import { exit as exit4 } from "node:process";
261
212
  import { promisify as promisify3 } from "node:util";
262
213
  var execAsync3 = promisify3(exec3);
263
- async function deleteGarden(name, options = {}) {
214
+ async function deleteWorktree(name, options = {}) {
264
215
  if (!name) {
265
- return { success: false, message: "Error: garden name required" };
216
+ return { success: false, message: "Error: worktree name required" };
266
217
  }
267
218
  const { force = false } = options;
268
219
  try {
269
220
  const gitRoot = await getGitRoot();
270
- const gardensPath = join3(gitRoot, ".git", "phantom", "gardens");
271
- const gardenPath = join3(gardensPath, name);
221
+ const worktreesPath = join3(gitRoot, ".git", "phantom", "worktrees");
222
+ const worktreePath = join3(worktreesPath, name);
272
223
  try {
273
- await access3(gardenPath);
224
+ await access3(worktreePath);
274
225
  } catch {
275
226
  return {
276
227
  success: false,
277
- message: `Error: Garden '${name}' does not exist`
228
+ message: `Error: Worktree '${name}' does not exist`
278
229
  };
279
230
  }
280
231
  let hasUncommittedChanges = false;
281
232
  let changedFiles = 0;
282
233
  try {
283
234
  const { stdout } = await execAsync3("git status --porcelain", {
284
- cwd: gardenPath
235
+ cwd: worktreePath
285
236
  });
286
237
  const changes = stdout.trim();
287
238
  if (changes) {
@@ -294,37 +245,37 @@ async function deleteGarden(name, options = {}) {
294
245
  if (hasUncommittedChanges && !force) {
295
246
  return {
296
247
  success: false,
297
- message: `Error: Garden '${name}' has uncommitted changes (${changedFiles} files). Use --force to delete anyway.`,
248
+ message: `Error: Worktree '${name}' has uncommitted changes (${changedFiles} files). Use --force to delete anyway.`,
298
249
  hasUncommittedChanges: true,
299
250
  changedFiles
300
251
  };
301
252
  }
302
253
  try {
303
- await execAsync3(`git worktree remove "${gardenPath}"`, {
254
+ await execAsync3(`git worktree remove "${worktreePath}"`, {
304
255
  cwd: gitRoot
305
256
  });
306
257
  } catch (error) {
307
258
  try {
308
- await execAsync3(`git worktree remove --force "${gardenPath}"`, {
259
+ await execAsync3(`git worktree remove --force "${worktreePath}"`, {
309
260
  cwd: gitRoot
310
261
  });
311
262
  } catch {
312
263
  return {
313
264
  success: false,
314
- message: `Error: Failed to remove worktree for garden '${name}'`
265
+ message: `Error: Failed to remove worktree '${name}'`
315
266
  };
316
267
  }
317
268
  }
318
- const branchName = `phantom/gardens/${name}`;
269
+ const branchName = `phantom/worktrees/${name}`;
319
270
  try {
320
271
  await execAsync3(`git branch -D "${branchName}"`, {
321
272
  cwd: gitRoot
322
273
  });
323
274
  } catch {
324
275
  }
325
- let message = `Deleted garden '${name}' and its branch '${branchName}'`;
276
+ let message = `Deleted worktree '${name}' and its branch '${branchName}'`;
326
277
  if (hasUncommittedChanges) {
327
- message = `Warning: Garden '${name}' had uncommitted changes (${changedFiles} files)
278
+ message = `Warning: Worktree '${name}' had uncommitted changes (${changedFiles} files)
328
279
  ${message}`;
329
280
  }
330
281
  return {
@@ -337,49 +288,110 @@ ${message}`;
337
288
  const errorMessage = error instanceof Error ? error.message : String(error);
338
289
  return {
339
290
  success: false,
340
- message: `Error deleting garden: ${errorMessage}`
291
+ message: `Error deleting worktree: ${errorMessage}`
341
292
  };
342
293
  }
343
294
  }
344
- async function gardensDeleteHandler(args2) {
295
+ async function deleteHandler(args2) {
345
296
  const forceIndex = args2.indexOf("--force");
346
297
  const force = forceIndex !== -1;
347
298
  const filteredArgs = args2.filter((arg) => arg !== "--force");
348
299
  const name = filteredArgs[0];
349
- const result = await deleteGarden(name, { force });
300
+ const result = await deleteWorktree(name, { force });
350
301
  if (!result.success) {
351
302
  console.error(result.message);
352
- exit5(1);
303
+ exit4(1);
353
304
  }
354
305
  console.log(result.message);
355
306
  }
356
307
 
357
- // src/gardens/commands/list.ts
308
+ // src/commands/exec.ts
309
+ import { spawn as spawn2 } from "node:child_process";
310
+ import { exit as exit5 } from "node:process";
311
+ async function execInWorktree(worktreeName, command2) {
312
+ if (!worktreeName) {
313
+ return { success: false, message: "Error: worktree name required" };
314
+ }
315
+ if (!command2 || command2.length === 0) {
316
+ return { success: false, message: "Error: command required" };
317
+ }
318
+ const worktreeResult = await whereWorktree(worktreeName);
319
+ if (!worktreeResult.success) {
320
+ return { success: false, message: worktreeResult.message };
321
+ }
322
+ const worktreePath = worktreeResult.path;
323
+ const [cmd, ...args2] = command2;
324
+ return new Promise((resolve) => {
325
+ const childProcess = spawn2(cmd, args2, {
326
+ cwd: worktreePath,
327
+ stdio: "inherit"
328
+ });
329
+ childProcess.on("error", (error) => {
330
+ resolve({
331
+ success: false,
332
+ message: `Error executing command: ${error.message}`
333
+ });
334
+ });
335
+ childProcess.on("exit", (code, signal) => {
336
+ if (signal) {
337
+ resolve({
338
+ success: false,
339
+ message: `Command terminated by signal: ${signal}`,
340
+ exitCode: 128 + (signal === "SIGTERM" ? 15 : 1)
341
+ });
342
+ } else {
343
+ const exitCode = code ?? 0;
344
+ resolve({
345
+ success: exitCode === 0,
346
+ exitCode
347
+ });
348
+ }
349
+ });
350
+ });
351
+ }
352
+ async function execHandler(args2) {
353
+ if (args2.length < 2) {
354
+ console.error("Usage: phantom exec <worktree-name> <command> [args...]");
355
+ exit5(1);
356
+ }
357
+ const worktreeName = args2[0];
358
+ const command2 = args2.slice(1);
359
+ const result = await execInWorktree(worktreeName, command2);
360
+ if (!result.success) {
361
+ if (result.message) {
362
+ console.error(result.message);
363
+ }
364
+ exit5(result.exitCode ?? 1);
365
+ }
366
+ exit5(result.exitCode ?? 0);
367
+ }
368
+
369
+ // src/commands/list.ts
358
370
  import { exec as exec4 } from "node:child_process";
359
371
  import { access as access4, readdir } from "node:fs/promises";
360
372
  import { join as join4 } from "node:path";
361
373
  import { promisify as promisify4 } from "node:util";
362
374
  var execAsync4 = promisify4(exec4);
363
- async function listGardens() {
375
+ async function listWorktrees() {
364
376
  try {
365
377
  const gitRoot = await getGitRoot();
366
- const gardensPath = join4(gitRoot, ".git", "phantom", "gardens");
378
+ const worktreesPath = join4(gitRoot, ".git", "phantom", "worktrees");
367
379
  try {
368
- await access4(gardensPath);
380
+ await access4(worktreesPath);
369
381
  } catch {
370
382
  return {
371
383
  success: true,
372
- gardens: [],
373
- message: "No gardens found (gardens directory doesn't exist)"
384
+ worktrees: [],
385
+ message: "No worktrees found (worktrees directory doesn't exist)"
374
386
  };
375
387
  }
376
- let gardenNames;
388
+ let worktreeNames;
377
389
  try {
378
- const entries = await readdir(gardensPath);
390
+ const entries = await readdir(worktreesPath);
379
391
  const validEntries = await Promise.all(
380
392
  entries.map(async (entry) => {
381
393
  try {
382
- const entryPath = join4(gardensPath, entry);
394
+ const entryPath = join4(worktreesPath, entry);
383
395
  await access4(entryPath);
384
396
  return entry;
385
397
  } catch {
@@ -387,30 +399,30 @@ async function listGardens() {
387
399
  }
388
400
  })
389
401
  );
390
- gardenNames = validEntries.filter(
402
+ worktreeNames = validEntries.filter(
391
403
  (entry) => entry !== null
392
404
  );
393
405
  } catch {
394
406
  return {
395
407
  success: true,
396
- gardens: [],
397
- message: "No gardens found (unable to read gardens directory)"
408
+ worktrees: [],
409
+ message: "No worktrees found (unable to read worktrees directory)"
398
410
  };
399
411
  }
400
- if (gardenNames.length === 0) {
412
+ if (worktreeNames.length === 0) {
401
413
  return {
402
414
  success: true,
403
- gardens: [],
404
- message: "No gardens found"
415
+ worktrees: [],
416
+ message: "No worktrees found"
405
417
  };
406
418
  }
407
- const gardens = await Promise.all(
408
- gardenNames.map(async (name) => {
409
- const gardenPath = join4(gardensPath, name);
419
+ const worktrees = await Promise.all(
420
+ worktreeNames.map(async (name) => {
421
+ const worktreePath = join4(worktreesPath, name);
410
422
  let branch = "unknown";
411
423
  try {
412
424
  const { stdout } = await execAsync4("git branch --show-current", {
413
- cwd: gardenPath
425
+ cwd: worktreePath
414
426
  });
415
427
  branch = stdout.trim() || "detached HEAD";
416
428
  } catch {
@@ -420,7 +432,7 @@ async function listGardens() {
420
432
  let changedFiles;
421
433
  try {
422
434
  const { stdout } = await execAsync4("git status --porcelain", {
423
- cwd: gardenPath
435
+ cwd: worktreePath
424
436
  });
425
437
  const changes = stdout.trim();
426
438
  if (changes) {
@@ -440,86 +452,146 @@ async function listGardens() {
440
452
  );
441
453
  return {
442
454
  success: true,
443
- gardens
455
+ worktrees
444
456
  };
445
457
  } catch (error) {
446
458
  const errorMessage = error instanceof Error ? error.message : String(error);
447
459
  return {
448
460
  success: false,
449
- message: `Error listing gardens: ${errorMessage}`
461
+ message: `Error listing worktrees: ${errorMessage}`
450
462
  };
451
463
  }
452
464
  }
453
- async function gardensListHandler() {
454
- const result = await listGardens();
465
+ async function listHandler() {
466
+ const result = await listWorktrees();
455
467
  if (!result.success) {
456
468
  console.error(result.message);
457
469
  return;
458
470
  }
459
- if (!result.gardens || result.gardens.length === 0) {
460
- console.log(result.message || "No gardens found");
471
+ if (!result.worktrees || result.worktrees.length === 0) {
472
+ console.log(result.message || "No worktrees found");
461
473
  return;
462
474
  }
463
- console.log("Gardens:");
464
- for (const garden of result.gardens) {
465
- const statusText = garden.status === "clean" ? "[clean]" : `[dirty: ${garden.changedFiles} files]`;
475
+ console.log("Worktrees:");
476
+ for (const worktree of result.worktrees) {
477
+ const statusText = worktree.status === "clean" ? "[clean]" : `[dirty: ${worktree.changedFiles} files]`;
466
478
  console.log(
467
- ` ${garden.name.padEnd(20)} (branch: ${garden.branch.padEnd(20)}) ${statusText}`
479
+ ` ${worktree.name.padEnd(20)} (branch: ${worktree.branch.padEnd(20)}) ${statusText}`
468
480
  );
469
481
  }
470
482
  console.log(`
471
- Total: ${result.gardens.length} gardens`);
483
+ Total: ${result.worktrees.length} worktrees`);
484
+ }
485
+
486
+ // package.json
487
+ var package_default = {
488
+ name: "@aku11i/phantom",
489
+ packageManager: "pnpm@10.8.1",
490
+ version: "0.3.0",
491
+ description: "A powerful CLI tool for managing Git worktrees for parallel development",
492
+ keywords: [
493
+ "git",
494
+ "worktree",
495
+ "cli",
496
+ "phantom",
497
+ "workspace",
498
+ "development",
499
+ "parallel"
500
+ ],
501
+ homepage: "https://github.com/aku11i/phantom#readme",
502
+ bugs: {
503
+ url: "https://github.com/aku11i/phantom/issues"
504
+ },
505
+ repository: {
506
+ type: "git",
507
+ url: "git+https://github.com/aku11i/phantom.git"
508
+ },
509
+ license: "MIT",
510
+ author: "aku11i",
511
+ type: "module",
512
+ bin: {
513
+ phantom: "./dist/phantom.js"
514
+ },
515
+ scripts: {
516
+ start: "node ./src/bin/phantom.ts",
517
+ phantom: "node ./src/bin/phantom.ts",
518
+ build: "node build.ts",
519
+ "type-check": "tsc --noEmit",
520
+ test: "node --test --experimental-strip-types --experimental-test-module-mocks src/**/*.test.ts",
521
+ lint: "biome check .",
522
+ fix: "biome check --write .",
523
+ ready: "pnpm fix && pnpm type-check && pnpm test",
524
+ "ready:check": "pnpm lint && pnpm type-check && pnpm test",
525
+ prepublishOnly: "pnpm ready:check && pnpm build"
526
+ },
527
+ engines: {
528
+ node: ">=22.0.0"
529
+ },
530
+ files: [
531
+ "dist/",
532
+ "README.md",
533
+ "LICENSE"
534
+ ],
535
+ devDependencies: {
536
+ "@biomejs/biome": "^1.9.4",
537
+ "@types/node": "^22.15.29",
538
+ esbuild: "^0.25.5",
539
+ typescript: "^5.8.3"
540
+ }
541
+ };
542
+
543
+ // src/commands/version.ts
544
+ function getVersion() {
545
+ return package_default.version;
546
+ }
547
+ function versionHandler() {
548
+ const version = getVersion();
549
+ console.log(`Phantom v${version}`);
472
550
  }
473
551
 
474
552
  // src/bin/phantom.ts
475
553
  var commands = [
476
554
  {
477
- name: "garden",
478
- description: "Manage git worktrees (gardens)",
479
- subcommands: [
480
- {
481
- name: "create",
482
- description: "Create a new worktree (garden)",
483
- handler: gardensCreateHandler
484
- },
485
- {
486
- name: "list",
487
- description: "List all gardens",
488
- handler: gardensListHandler
489
- },
490
- {
491
- name: "where",
492
- description: "Output the path of a specific garden",
493
- handler: gardensWhereHandler
494
- },
495
- {
496
- name: "delete",
497
- description: "Delete a garden (use --force for dirty gardens)",
498
- handler: gardensDeleteHandler
499
- }
500
- ]
555
+ name: "create",
556
+ description: "Create a new worktree [--shell to open shell]",
557
+ handler: createHandler
558
+ },
559
+ {
560
+ name: "list",
561
+ description: "List all worktrees",
562
+ handler: listHandler
563
+ },
564
+ {
565
+ name: "where",
566
+ description: "Output the path of a specific worktree",
567
+ handler: whereHandler
568
+ },
569
+ {
570
+ name: "delete",
571
+ description: "Delete a worktree (use --force for uncommitted changes)",
572
+ handler: deleteHandler
501
573
  },
502
574
  {
503
575
  name: "exec",
504
- description: "Execute a command in a garden directory",
576
+ description: "Execute a command in a worktree directory",
505
577
  handler: execHandler
506
578
  },
507
579
  {
508
580
  name: "shell",
509
- description: "Open interactive shell in a garden directory",
581
+ description: "Open interactive shell in a worktree directory",
510
582
  handler: shellHandler
583
+ },
584
+ {
585
+ name: "version",
586
+ description: "Display phantom version",
587
+ handler: versionHandler
511
588
  }
512
589
  ];
513
- function printHelp(commands2, prefix = "") {
590
+ function printHelp(commands2) {
514
591
  console.log("Usage: phantom <command> [options]\n");
515
592
  console.log("Commands:");
516
593
  for (const cmd of commands2) {
517
- console.log(` ${prefix}${cmd.name.padEnd(20)} ${cmd.description}`);
518
- if (cmd.subcommands) {
519
- for (const subcmd of cmd.subcommands) {
520
- console.log(` ${subcmd.name.padEnd(18)} ${subcmd.description}`);
521
- }
522
- }
594
+ console.log(` ${cmd.name.padEnd(12)} ${cmd.description}`);
523
595
  }
524
596
  }
525
597
  function findCommand(args2, commands2) {
@@ -547,6 +619,10 @@ if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
547
619
  printHelp(commands);
548
620
  exit6(0);
549
621
  }
622
+ if (args[0] === "--version" || args[0] === "-v") {
623
+ versionHandler();
624
+ exit6(0);
625
+ }
550
626
  var { command, remainingArgs } = findCommand(args, commands);
551
627
  if (!command || !command.handler) {
552
628
  console.error(`Error: Unknown command '${args.join(" ")}'