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