@aku11i/phantom 0.6.0 → 0.7.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
@@ -44,9 +44,9 @@ async function executeGitCommandInDirectory(directory, args2) {
44
44
 
45
45
  // src/core/git/libs/get-git-root.ts
46
46
  async function getGitRoot() {
47
- const { stdout } = await executeGitCommand(["rev-parse", "--git-common-dir"]);
48
- if (stdout.endsWith("/.git") || stdout === ".git") {
49
- return resolve(process.cwd(), dirname(stdout));
47
+ const { stdout: stdout2 } = await executeGitCommand(["rev-parse", "--git-common-dir"]);
48
+ if (stdout2.endsWith("/.git") || stdout2 === ".git") {
49
+ return resolve(process.cwd(), dirname(stdout2));
50
50
  }
51
51
  const { stdout: toplevel } = await executeGitCommand([
52
52
  "rev-parse",
@@ -216,18 +216,20 @@ async function spawnProcess(config) {
216
216
  }
217
217
 
218
218
  // src/core/process/exec.ts
219
- async function execInWorktree(gitRoot, worktreeName, command2) {
219
+ async function execInWorktree(gitRoot, worktreeName, command2, options = {}) {
220
220
  const validation = await validateWorktreeExists(gitRoot, worktreeName);
221
221
  if (!validation.exists) {
222
222
  return err(new WorktreeNotFoundError(worktreeName));
223
223
  }
224
224
  const worktreePath = validation.path;
225
225
  const [cmd, ...args2] = command2;
226
+ const stdio = options.interactive ? "inherit" : ["ignore", "inherit", "inherit"];
226
227
  return spawnProcess({
227
228
  command: cmd,
228
229
  args: args2,
229
230
  options: {
230
- cwd: worktreePath
231
+ cwd: worktreePath,
232
+ stdio
231
233
  }
232
234
  });
233
235
  }
@@ -407,7 +409,8 @@ async function attachHandler(args2) {
407
409
  const execResult = await execInWorktree(
408
410
  gitRoot,
409
411
  branchName,
410
- values.exec.split(" ")
412
+ values.exec.split(" "),
413
+ { interactive: true }
411
414
  );
412
415
  if (isErr(execResult)) {
413
416
  exitWithError(execResult.error.message, exitCodes.generalError);
@@ -458,6 +461,20 @@ function validateConfig(config) {
458
461
  );
459
462
  }
460
463
  }
464
+ if (postCreate.commands !== void 0) {
465
+ if (!Array.isArray(postCreate.commands)) {
466
+ return err(
467
+ new ConfigValidationError("postCreate.commands must be an array")
468
+ );
469
+ }
470
+ if (!postCreate.commands.every((c) => typeof c === "string")) {
471
+ return err(
472
+ new ConfigValidationError(
473
+ "postCreate.commands must contain only strings"
474
+ )
475
+ );
476
+ }
477
+ }
461
478
  }
462
479
  return ok(config);
463
480
  }
@@ -741,17 +758,42 @@ async function createHandler(args2) {
741
758
  Warning: Failed to copy some files: ${result.value.copyError}`
742
759
  );
743
760
  }
761
+ if (isOk(configResult) && configResult.value.postCreate?.commands) {
762
+ const commands2 = configResult.value.postCreate.commands;
763
+ output.log("\nRunning post-create commands...");
764
+ for (const command2 of commands2) {
765
+ output.log(`Executing: ${command2}`);
766
+ const shell = process.env.SHELL || "/bin/sh";
767
+ const cmdResult = await execInWorktree(gitRoot, worktreeName, [
768
+ shell,
769
+ "-c",
770
+ command2
771
+ ]);
772
+ if (isErr(cmdResult)) {
773
+ output.error(`Failed to execute command: ${cmdResult.error.message}`);
774
+ const exitCode = "exitCode" in cmdResult.error ? cmdResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
775
+ exitWithError(`Post-create command failed: ${command2}`, exitCode);
776
+ }
777
+ if (cmdResult.value.exitCode !== 0) {
778
+ exitWithError(
779
+ `Post-create command failed: ${command2}`,
780
+ cmdResult.value.exitCode
781
+ );
782
+ }
783
+ }
784
+ }
744
785
  if (execCommand && isOk(result)) {
745
786
  output.log(
746
787
  `
747
788
  Executing command in worktree '${worktreeName}': ${execCommand}`
748
789
  );
749
790
  const shell = process.env.SHELL || "/bin/sh";
750
- const execResult = await execInWorktree(gitRoot, worktreeName, [
751
- shell,
752
- "-c",
753
- execCommand
754
- ]);
791
+ const execResult = await execInWorktree(
792
+ gitRoot,
793
+ worktreeName,
794
+ [shell, "-c", execCommand],
795
+ { interactive: true }
796
+ );
755
797
  if (isErr(execResult)) {
756
798
  output.error(execResult.error.message);
757
799
  const exitCode = "exitCode" in execResult.error ? execResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
@@ -809,14 +851,14 @@ import { parseArgs as parseArgs3 } from "node:util";
809
851
 
810
852
  // src/core/git/libs/list-worktrees.ts
811
853
  async function listWorktrees(gitRoot) {
812
- const { stdout } = await executeGitCommand([
854
+ const { stdout: stdout2 } = await executeGitCommand([
813
855
  "worktree",
814
856
  "list",
815
857
  "--porcelain"
816
858
  ]);
817
859
  const worktrees = [];
818
860
  let currentWorktree = {};
819
- const lines = stdout.split("\n").filter((line) => line.length > 0);
861
+ const lines = stdout2.split("\n").filter((line) => line.length > 0);
820
862
  for (const line of lines) {
821
863
  if (line.startsWith("worktree ")) {
822
864
  if (currentWorktree.path) {
@@ -870,14 +912,14 @@ async function getCurrentWorktree(gitRoot) {
870
912
  // src/core/worktree/delete.ts
871
913
  async function getWorktreeStatus(worktreePath) {
872
914
  try {
873
- const { stdout } = await executeGitCommandInDirectory(worktreePath, [
915
+ const { stdout: stdout2 } = await executeGitCommandInDirectory(worktreePath, [
874
916
  "status",
875
917
  "--porcelain"
876
918
  ]);
877
- if (stdout) {
919
+ if (stdout2) {
878
920
  return {
879
921
  hasUncommittedChanges: true,
880
- changedFiles: stdout.split("\n").length
922
+ changedFiles: stdout2.split("\n").length
881
923
  };
882
924
  }
883
925
  } catch {
@@ -1033,7 +1075,12 @@ async function execHandler(args2) {
1033
1075
  const [worktreeName, ...commandArgs] = positionals;
1034
1076
  try {
1035
1077
  const gitRoot = await getGitRoot();
1036
- const result = await execInWorktree(gitRoot, worktreeName, commandArgs);
1078
+ const result = await execInWorktree(
1079
+ gitRoot,
1080
+ worktreeName,
1081
+ commandArgs,
1082
+ { interactive: true }
1083
+ );
1037
1084
  if (isErr(result)) {
1038
1085
  const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.notFound : result.error.exitCode || exitCodes.generalError;
1039
1086
  exitWithError(result.error.message, exitCode);
@@ -1053,11 +1100,11 @@ import { parseArgs as parseArgs5 } from "node:util";
1053
1100
  // src/core/worktree/list.ts
1054
1101
  async function getWorktreeStatus2(worktreePath) {
1055
1102
  try {
1056
- const { stdout } = await executeGitCommandInDirectory(worktreePath, [
1103
+ const { stdout: stdout2 } = await executeGitCommandInDirectory(worktreePath, [
1057
1104
  "status",
1058
1105
  "--porcelain"
1059
1106
  ]);
1060
- return !stdout;
1107
+ return !stdout2;
1061
1108
  } catch {
1062
1109
  return true;
1063
1110
  }
@@ -1179,7 +1226,7 @@ import { parseArgs as parseArgs7 } from "node:util";
1179
1226
  var package_default = {
1180
1227
  name: "@aku11i/phantom",
1181
1228
  packageManager: "pnpm@10.8.1",
1182
- version: "0.6.0",
1229
+ version: "0.7.0",
1183
1230
  description: "A powerful CLI tool for managing Git worktrees for parallel development",
1184
1231
  keywords: [
1185
1232
  "git",
@@ -1295,55 +1342,446 @@ async function whereHandler(args2) {
1295
1342
  }
1296
1343
  }
1297
1344
 
1345
+ // src/cli/help.ts
1346
+ import { stdout } from "node:process";
1347
+ var HelpFormatter = class {
1348
+ width;
1349
+ indent = " ";
1350
+ constructor() {
1351
+ this.width = stdout.columns || 80;
1352
+ }
1353
+ formatMainHelp(commands2) {
1354
+ const lines = [];
1355
+ lines.push(this.bold("Phantom - Git Worktree Manager"));
1356
+ lines.push("");
1357
+ lines.push(
1358
+ this.dim(
1359
+ "A CLI tool for managing Git worktrees with enhanced functionality"
1360
+ )
1361
+ );
1362
+ lines.push("");
1363
+ lines.push(this.section("USAGE"));
1364
+ lines.push(`${this.indent}phantom <command> [options]`);
1365
+ lines.push("");
1366
+ lines.push(this.section("COMMANDS"));
1367
+ const maxNameLength = Math.max(...commands2.map((cmd) => cmd.name.length));
1368
+ for (const cmd of commands2) {
1369
+ const paddedName = cmd.name.padEnd(maxNameLength + 2);
1370
+ lines.push(`${this.indent}${this.cyan(paddedName)}${cmd.description}`);
1371
+ }
1372
+ lines.push("");
1373
+ lines.push(this.section("GLOBAL OPTIONS"));
1374
+ const helpOption = "-h, --help";
1375
+ const versionOption = "-v, --version";
1376
+ const globalOptionWidth = Math.max(helpOption.length, versionOption.length) + 2;
1377
+ lines.push(
1378
+ `${this.indent}${this.cyan(helpOption.padEnd(globalOptionWidth))}Show help`
1379
+ );
1380
+ lines.push(
1381
+ `${this.indent}${this.cyan(versionOption.padEnd(globalOptionWidth))}Show version`
1382
+ );
1383
+ lines.push("");
1384
+ lines.push(
1385
+ this.dim(
1386
+ "Run 'phantom <command> --help' for more information on a command."
1387
+ )
1388
+ );
1389
+ return lines.join("\n");
1390
+ }
1391
+ formatCommandHelp(help) {
1392
+ const lines = [];
1393
+ lines.push(this.bold(`phantom ${help.name}`));
1394
+ lines.push(this.dim(help.description));
1395
+ lines.push("");
1396
+ lines.push(this.section("USAGE"));
1397
+ lines.push(`${this.indent}${help.usage}`);
1398
+ lines.push("");
1399
+ if (help.options && help.options.length > 0) {
1400
+ lines.push(this.section("OPTIONS"));
1401
+ const maxOptionLength = Math.max(
1402
+ ...help.options.map((opt) => this.formatOptionName(opt).length)
1403
+ );
1404
+ for (const option of help.options) {
1405
+ const optionName = this.formatOptionName(option);
1406
+ const paddedName = optionName.padEnd(maxOptionLength + 2);
1407
+ const description = this.wrapText(
1408
+ option.description,
1409
+ maxOptionLength + 4
1410
+ );
1411
+ lines.push(`${this.indent}${this.cyan(paddedName)}${description[0]}`);
1412
+ for (let i = 1; i < description.length; i++) {
1413
+ lines.push(
1414
+ `${this.indent}${" ".repeat(maxOptionLength + 2)}${description[i]}`
1415
+ );
1416
+ }
1417
+ if (option.example) {
1418
+ const exampleIndent = " ".repeat(maxOptionLength + 4);
1419
+ lines.push(
1420
+ `${this.indent}${exampleIndent}${this.dim(`Example: ${option.example}`)}`
1421
+ );
1422
+ }
1423
+ }
1424
+ lines.push("");
1425
+ }
1426
+ if (help.examples && help.examples.length > 0) {
1427
+ lines.push(this.section("EXAMPLES"));
1428
+ for (const example of help.examples) {
1429
+ lines.push(`${this.indent}${this.dim(example.description)}`);
1430
+ lines.push(`${this.indent}${this.indent}$ ${example.command}`);
1431
+ lines.push("");
1432
+ }
1433
+ }
1434
+ if (help.notes && help.notes.length > 0) {
1435
+ lines.push(this.section("NOTES"));
1436
+ for (const note of help.notes) {
1437
+ const wrappedNote = this.wrapText(note, 2);
1438
+ for (const line of wrappedNote) {
1439
+ lines.push(`${this.indent}${line}`);
1440
+ }
1441
+ }
1442
+ lines.push("");
1443
+ }
1444
+ return lines.join("\n");
1445
+ }
1446
+ formatOptionName(option) {
1447
+ const parts = [];
1448
+ if (option.short) {
1449
+ parts.push(`-${option.short},`);
1450
+ }
1451
+ parts.push(`--${option.name}`);
1452
+ if (option.type === "string") {
1453
+ parts.push(option.multiple ? "<value>..." : "<value>");
1454
+ }
1455
+ return parts.join(" ");
1456
+ }
1457
+ wrapText(text, indent) {
1458
+ const maxWidth = this.width - indent - 2;
1459
+ const words = text.split(" ");
1460
+ const lines = [];
1461
+ let currentLine = "";
1462
+ for (const word of words) {
1463
+ if (currentLine.length + word.length + 1 > maxWidth) {
1464
+ lines.push(currentLine);
1465
+ currentLine = word;
1466
+ } else {
1467
+ currentLine = currentLine ? `${currentLine} ${word}` : word;
1468
+ }
1469
+ }
1470
+ if (currentLine) {
1471
+ lines.push(currentLine);
1472
+ }
1473
+ return lines;
1474
+ }
1475
+ section(text) {
1476
+ return this.bold(text);
1477
+ }
1478
+ bold(text) {
1479
+ return `\x1B[1m${text}\x1B[0m`;
1480
+ }
1481
+ dim(text) {
1482
+ return `\x1B[2m${text}\x1B[0m`;
1483
+ }
1484
+ cyan(text) {
1485
+ return `\x1B[36m${text}\x1B[0m`;
1486
+ }
1487
+ };
1488
+ var helpFormatter = new HelpFormatter();
1489
+
1490
+ // src/cli/help/attach.ts
1491
+ var attachHelp = {
1492
+ name: "attach",
1493
+ description: "Attach to an existing branch by creating a new worktree",
1494
+ usage: "phantom attach <worktree-name> <branch-name> [options]",
1495
+ options: [
1496
+ {
1497
+ name: "shell",
1498
+ short: "s",
1499
+ type: "boolean",
1500
+ description: "Open an interactive shell in the worktree after attaching"
1501
+ },
1502
+ {
1503
+ name: "exec",
1504
+ short: "x",
1505
+ type: "string",
1506
+ description: "Execute a command in the worktree after attaching",
1507
+ example: "--exec 'git pull'"
1508
+ }
1509
+ ],
1510
+ examples: [
1511
+ {
1512
+ description: "Attach to an existing branch",
1513
+ command: "phantom attach review-pr main"
1514
+ },
1515
+ {
1516
+ description: "Attach to a remote branch and open a shell",
1517
+ command: "phantom attach hotfix origin/hotfix-v1.2 --shell"
1518
+ },
1519
+ {
1520
+ description: "Attach to a branch and pull latest changes",
1521
+ command: "phantom attach staging origin/staging --exec 'git pull'"
1522
+ }
1523
+ ],
1524
+ notes: [
1525
+ "The branch must already exist (locally or remotely)",
1526
+ "If attaching to a remote branch, it will be checked out locally",
1527
+ "Only one of --shell or --exec options can be used at a time"
1528
+ ]
1529
+ };
1530
+
1531
+ // src/cli/help/create.ts
1532
+ var createHelp = {
1533
+ name: "create",
1534
+ description: "Create a new Git worktree (phantom)",
1535
+ usage: "phantom create <name> [options]",
1536
+ options: [
1537
+ {
1538
+ name: "shell",
1539
+ short: "s",
1540
+ type: "boolean",
1541
+ description: "Open an interactive shell in the new worktree after creation"
1542
+ },
1543
+ {
1544
+ name: "exec",
1545
+ short: "x",
1546
+ type: "string",
1547
+ description: "Execute a command in the new worktree after creation",
1548
+ example: "--exec 'npm install'"
1549
+ },
1550
+ {
1551
+ name: "tmux",
1552
+ short: "t",
1553
+ type: "boolean",
1554
+ description: "Open the worktree in a new tmux window (requires being inside tmux)"
1555
+ },
1556
+ {
1557
+ name: "tmux-vertical",
1558
+ type: "boolean",
1559
+ description: "Open the worktree in a vertical tmux pane (requires being inside tmux)"
1560
+ },
1561
+ {
1562
+ name: "tmux-horizontal",
1563
+ type: "boolean",
1564
+ description: "Open the worktree in a horizontal tmux pane (requires being inside tmux)"
1565
+ },
1566
+ {
1567
+ name: "copy-file",
1568
+ type: "string",
1569
+ multiple: true,
1570
+ description: "Copy specified files from the current worktree to the new one. Can be used multiple times",
1571
+ example: "--copy-file .env --copy-file config.local.json"
1572
+ }
1573
+ ],
1574
+ examples: [
1575
+ {
1576
+ description: "Create a new worktree named 'feature-auth'",
1577
+ command: "phantom create feature-auth"
1578
+ },
1579
+ {
1580
+ description: "Create a worktree and open a shell in it",
1581
+ command: "phantom create bugfix-123 --shell"
1582
+ },
1583
+ {
1584
+ description: "Create a worktree and run npm install",
1585
+ command: "phantom create new-feature --exec 'npm install'"
1586
+ },
1587
+ {
1588
+ description: "Create a worktree in a new tmux window",
1589
+ command: "phantom create experiment --tmux"
1590
+ },
1591
+ {
1592
+ description: "Create a worktree and copy environment files",
1593
+ command: "phantom create staging --copy-file .env --copy-file database.yml"
1594
+ }
1595
+ ],
1596
+ notes: [
1597
+ "The worktree name will be used as the branch name",
1598
+ "Only one of --shell, --exec, or --tmux options can be used at a time",
1599
+ "File copying can also be configured in phantom.config.json"
1600
+ ]
1601
+ };
1602
+
1603
+ // src/cli/help/delete.ts
1604
+ var deleteHelp = {
1605
+ name: "delete",
1606
+ description: "Delete a Git worktree (phantom)",
1607
+ usage: "phantom delete <name> [options]",
1608
+ options: [
1609
+ {
1610
+ name: "force",
1611
+ short: "f",
1612
+ type: "boolean",
1613
+ description: "Force deletion even if the worktree has uncommitted or unpushed changes"
1614
+ }
1615
+ ],
1616
+ examples: [
1617
+ {
1618
+ description: "Delete a worktree",
1619
+ command: "phantom delete feature-auth"
1620
+ },
1621
+ {
1622
+ description: "Force delete a worktree with uncommitted changes",
1623
+ command: "phantom delete experimental --force"
1624
+ }
1625
+ ],
1626
+ notes: [
1627
+ "By default, deletion will fail if the worktree has uncommitted changes",
1628
+ "The associated branch will also be deleted if it's not checked out elsewhere"
1629
+ ]
1630
+ };
1631
+
1632
+ // src/cli/help/exec.ts
1633
+ var execHelp = {
1634
+ name: "exec",
1635
+ description: "Execute a command in a worktree directory",
1636
+ usage: "phantom exec <worktree-name> <command> [args...]",
1637
+ examples: [
1638
+ {
1639
+ description: "Run npm test in a worktree",
1640
+ command: "phantom exec feature-auth npm test"
1641
+ },
1642
+ {
1643
+ description: "Check git status in a worktree",
1644
+ command: "phantom exec bugfix-123 git status"
1645
+ },
1646
+ {
1647
+ description: "Run a complex command with arguments",
1648
+ command: "phantom exec staging npm run build -- --production"
1649
+ }
1650
+ ],
1651
+ notes: [
1652
+ "The command is executed with the worktree directory as the working directory",
1653
+ "All arguments after the worktree name are passed to the command",
1654
+ "The exit code of the executed command is preserved"
1655
+ ]
1656
+ };
1657
+
1658
+ // src/cli/help/list.ts
1659
+ var listHelp = {
1660
+ name: "list",
1661
+ description: "List all Git worktrees (phantoms)",
1662
+ usage: "phantom list",
1663
+ examples: [
1664
+ {
1665
+ description: "List all worktrees",
1666
+ command: "phantom list"
1667
+ }
1668
+ ],
1669
+ notes: [
1670
+ "Shows all worktrees with their paths and associated branches",
1671
+ "The main worktree is marked as '(bare)' if using a bare repository"
1672
+ ]
1673
+ };
1674
+
1675
+ // src/cli/help/shell.ts
1676
+ var shellHelp = {
1677
+ name: "shell",
1678
+ description: "Open an interactive shell in a worktree directory",
1679
+ usage: "phantom shell <worktree-name>",
1680
+ examples: [
1681
+ {
1682
+ description: "Open a shell in a worktree",
1683
+ command: "phantom shell feature-auth"
1684
+ }
1685
+ ],
1686
+ notes: [
1687
+ "Uses your default shell from the SHELL environment variable",
1688
+ "The shell starts with the worktree directory as the working directory",
1689
+ "Type 'exit' to return to your original directory"
1690
+ ]
1691
+ };
1692
+
1693
+ // src/cli/help/version.ts
1694
+ var versionHelp = {
1695
+ name: "version",
1696
+ description: "Display phantom version information",
1697
+ usage: "phantom version",
1698
+ examples: [
1699
+ {
1700
+ description: "Show version",
1701
+ command: "phantom version"
1702
+ }
1703
+ ],
1704
+ notes: ["Also accessible via 'phantom --version' or 'phantom -v'"]
1705
+ };
1706
+
1707
+ // src/cli/help/where.ts
1708
+ var whereHelp = {
1709
+ name: "where",
1710
+ description: "Output the filesystem path of a specific worktree",
1711
+ usage: "phantom where <worktree-name>",
1712
+ examples: [
1713
+ {
1714
+ description: "Get the path of a worktree",
1715
+ command: "phantom where feature-auth"
1716
+ },
1717
+ {
1718
+ description: "Change directory to a worktree",
1719
+ command: "cd $(phantom where staging)"
1720
+ }
1721
+ ],
1722
+ notes: [
1723
+ "Outputs only the path, making it suitable for use in scripts",
1724
+ "Exits with an error code if the worktree doesn't exist"
1725
+ ]
1726
+ };
1727
+
1298
1728
  // src/bin/phantom.ts
1299
1729
  var commands = [
1300
1730
  {
1301
1731
  name: "create",
1302
- description: "Create a new worktree [--shell | --exec <command> | --tmux | --tmux-vertical | --tmux-horizontal] [--copy-file <file>]...",
1303
- handler: createHandler
1732
+ description: "Create a new Git worktree (phantom)",
1733
+ handler: createHandler,
1734
+ help: createHelp
1304
1735
  },
1305
1736
  {
1306
1737
  name: "attach",
1307
- description: "Attach to an existing branch [--shell | --exec <command>]",
1308
- handler: attachHandler
1738
+ description: "Attach to an existing branch by creating a new worktree",
1739
+ handler: attachHandler,
1740
+ help: attachHelp
1309
1741
  },
1310
1742
  {
1311
1743
  name: "list",
1312
- description: "List all worktrees",
1313
- handler: listHandler
1744
+ description: "List all Git worktrees (phantoms)",
1745
+ handler: listHandler,
1746
+ help: listHelp
1314
1747
  },
1315
1748
  {
1316
1749
  name: "where",
1317
- description: "Output the path of a specific worktree",
1318
- handler: whereHandler
1750
+ description: "Output the filesystem path of a specific worktree",
1751
+ handler: whereHandler,
1752
+ help: whereHelp
1319
1753
  },
1320
1754
  {
1321
1755
  name: "delete",
1322
- description: "Delete a worktree (use --force for uncommitted changes)",
1323
- handler: deleteHandler
1756
+ description: "Delete a Git worktree (phantom)",
1757
+ handler: deleteHandler,
1758
+ help: deleteHelp
1324
1759
  },
1325
1760
  {
1326
1761
  name: "exec",
1327
1762
  description: "Execute a command in a worktree directory",
1328
- handler: execHandler
1763
+ handler: execHandler,
1764
+ help: execHelp
1329
1765
  },
1330
1766
  {
1331
1767
  name: "shell",
1332
- description: "Open interactive shell in a worktree directory",
1333
- handler: shellHandler
1768
+ description: "Open an interactive shell in a worktree directory",
1769
+ handler: shellHandler,
1770
+ help: shellHelp
1334
1771
  },
1335
1772
  {
1336
1773
  name: "version",
1337
- description: "Display phantom version",
1338
- handler: versionHandler
1774
+ description: "Display phantom version information",
1775
+ handler: versionHandler,
1776
+ help: versionHelp
1339
1777
  }
1340
1778
  ];
1341
1779
  function printHelp(commands2) {
1342
- console.log("Usage: phantom <command> [options]\n");
1343
- console.log("Commands:");
1344
- for (const cmd of commands2) {
1345
- console.log(` ${cmd.name.padEnd(12)} ${cmd.description}`);
1346
- }
1780
+ const simpleCommands = commands2.map((cmd) => ({
1781
+ name: cmd.name,
1782
+ description: cmd.description
1783
+ }));
1784
+ console.log(helpFormatter.formatMainHelp(simpleCommands));
1347
1785
  }
1348
1786
  function findCommand(args2, commands2) {
1349
1787
  if (args2.length === 0) {
@@ -1381,6 +1819,14 @@ if (!command || !command.handler) {
1381
1819
  printHelp(commands);
1382
1820
  exit(1);
1383
1821
  }
1822
+ if (remainingArgs.includes("--help") || remainingArgs.includes("-h")) {
1823
+ if (command.help) {
1824
+ console.log(helpFormatter.formatCommandHelp(command.help));
1825
+ } else {
1826
+ console.log(`Help not available for command '${command.name}'`);
1827
+ }
1828
+ exit(0);
1829
+ }
1384
1830
  try {
1385
1831
  await command.handler(remainingArgs);
1386
1832
  } catch (error) {