@aku11i/phantom 0.3.0 → 0.5.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
@@ -1,493 +1,877 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin/phantom.ts
4
- import { argv, exit as exit6 } from "node:process";
4
+ import { argv, exit } from "node:process";
5
5
 
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";
6
+ // src/cli/handlers/attach.ts
7
+ import { parseArgs } from "node:util";
10
8
 
11
- // src/git/libs/add-worktree.ts
12
- import { exec } from "node:child_process";
9
+ // src/core/git/libs/get-git-root.ts
10
+ import { dirname, resolve } from "node:path";
11
+
12
+ // src/core/git/executor.ts
13
+ import { execFile as execFileCallback } from "node:child_process";
13
14
  import { promisify } from "node:util";
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}`);
15
+ var execFile = promisify(execFileCallback);
16
+ async function executeGitCommand(args2, options = {}) {
17
+ try {
18
+ const result = await execFile("git", args2, {
19
+ cwd: options.cwd,
20
+ env: options.env || process.env,
21
+ encoding: "utf8"
22
+ });
23
+ return {
24
+ stdout: result.stdout.trim(),
25
+ stderr: result.stderr.trim()
26
+ };
27
+ } catch (error) {
28
+ if (error && typeof error === "object" && "stdout" in error && "stderr" in error) {
29
+ const execError = error;
30
+ if (execError.stderr?.trim()) {
31
+ throw new Error(execError.stderr.trim());
32
+ }
33
+ return {
34
+ stdout: execError.stdout?.trim() || "",
35
+ stderr: execError.stderr?.trim() || ""
36
+ };
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ async function executeGitCommandInDirectory(directory, args2) {
42
+ return executeGitCommand(["-C", directory, ...args2], {});
18
43
  }
19
44
 
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);
45
+ // src/core/git/libs/get-git-root.ts
24
46
  async function getGitRoot() {
25
- const { stdout } = await execAsync2("git rev-parse --show-toplevel");
26
- return stdout.trim();
47
+ const { stdout } = await executeGitCommand(["rev-parse", "--git-common-dir"]);
48
+ if (stdout.endsWith("/.git") || stdout === ".git") {
49
+ return resolve(process.cwd(), dirname(stdout));
50
+ }
51
+ const { stdout: toplevel } = await executeGitCommand([
52
+ "rev-parse",
53
+ "--show-toplevel"
54
+ ]);
55
+ return toplevel;
27
56
  }
28
57
 
29
- // src/commands/shell.ts
30
- import { spawn } from "node:child_process";
31
- import { exit as exit2 } from "node:process";
58
+ // src/core/types/result.ts
59
+ var ok = (value) => ({
60
+ ok: true,
61
+ value
62
+ });
63
+ var err = (error) => ({
64
+ ok: false,
65
+ error
66
+ });
67
+ var isOk = (result) => result.ok;
68
+ var isErr = (result) => !result.ok;
32
69
 
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) {
38
- if (!name) {
39
- return { success: false, message: "Error: worktree name required" };
70
+ // src/core/worktree/errors.ts
71
+ var WorktreeError = class extends Error {
72
+ constructor(message) {
73
+ super(message);
74
+ this.name = "WorktreeError";
75
+ }
76
+ };
77
+ var WorktreeNotFoundError = class extends WorktreeError {
78
+ constructor(name) {
79
+ super(`Worktree '${name}' not found`);
80
+ this.name = "WorktreeNotFoundError";
81
+ }
82
+ };
83
+ var WorktreeAlreadyExistsError = class extends WorktreeError {
84
+ constructor(name) {
85
+ super(`Worktree '${name}' already exists`);
86
+ this.name = "WorktreeAlreadyExistsError";
40
87
  }
88
+ };
89
+ var GitOperationError = class extends WorktreeError {
90
+ constructor(operation, details) {
91
+ super(`Git ${operation} failed: ${details}`);
92
+ this.name = "GitOperationError";
93
+ }
94
+ };
95
+ var BranchNotFoundError = class extends WorktreeError {
96
+ constructor(branchName) {
97
+ super(`Branch '${branchName}' not found`);
98
+ this.name = "BranchNotFoundError";
99
+ }
100
+ };
101
+
102
+ // src/core/worktree/validate.ts
103
+ import fs from "node:fs/promises";
104
+
105
+ // src/core/paths.ts
106
+ import { join } from "node:path";
107
+ function getPhantomDirectory(gitRoot) {
108
+ return join(gitRoot, ".git", "phantom", "worktrees");
109
+ }
110
+ function getWorktreePath(gitRoot, name) {
111
+ return join(getPhantomDirectory(gitRoot), name);
112
+ }
113
+
114
+ // src/core/worktree/validate.ts
115
+ async function validateWorktreeExists(gitRoot, name) {
116
+ const worktreePath = getWorktreePath(gitRoot, name);
41
117
  try {
42
- const gitRoot = await getGitRoot();
43
- const worktreesPath = join(gitRoot, ".git", "phantom", "worktrees");
44
- const worktreePath = join(worktreesPath, name);
45
- try {
46
- await access(worktreePath);
47
- } catch {
48
- return {
49
- success: false,
50
- message: `Error: Worktree '${name}' does not exist`
51
- };
52
- }
118
+ await fs.access(worktreePath);
53
119
  return {
54
- success: true,
120
+ exists: true,
55
121
  path: worktreePath
56
122
  };
57
- } catch (error) {
58
- const errorMessage = error instanceof Error ? error.message : String(error);
123
+ } catch {
59
124
  return {
60
- success: false,
61
- message: `Error locating worktree: ${errorMessage}`
125
+ exists: false,
126
+ message: `Worktree '${name}' does not exist`
62
127
  };
63
128
  }
64
129
  }
65
- async function whereHandler(args2) {
66
- const name = args2[0];
67
- const result = await whereWorktree(name);
68
- if (!result.success) {
69
- console.error(result.message);
70
- exit(1);
130
+ async function validateWorktreeDoesNotExist(gitRoot, name) {
131
+ const worktreePath = getWorktreePath(gitRoot, name);
132
+ try {
133
+ await fs.access(worktreePath);
134
+ return {
135
+ exists: true,
136
+ message: `Worktree '${name}' already exists`
137
+ };
138
+ } catch {
139
+ return {
140
+ exists: false,
141
+ path: worktreePath
142
+ };
143
+ }
144
+ }
145
+ function validateWorktreeName(name) {
146
+ if (!name || name.trim() === "") {
147
+ return err(new Error("Phantom name cannot be empty"));
148
+ }
149
+ if (name.includes("/")) {
150
+ return err(new Error("Phantom name cannot contain slashes"));
151
+ }
152
+ if (name.startsWith(".")) {
153
+ return err(new Error("Phantom name cannot start with a dot"));
71
154
  }
72
- console.log(result.path);
155
+ return ok(void 0);
73
156
  }
74
157
 
75
- // src/commands/shell.ts
76
- async function shellInWorktree(worktreeName) {
77
- if (!worktreeName) {
78
- return { success: false, message: "Error: worktree name required" };
158
+ // src/core/process/spawn.ts
159
+ import {
160
+ spawn as nodeSpawn
161
+ } from "node:child_process";
162
+
163
+ // src/core/process/errors.ts
164
+ var ProcessError = class extends Error {
165
+ exitCode;
166
+ constructor(message, exitCode) {
167
+ super(message);
168
+ this.name = "ProcessError";
169
+ this.exitCode = exitCode;
79
170
  }
80
- const worktreeResult = await whereWorktree(worktreeName);
81
- if (!worktreeResult.success) {
82
- return { success: false, message: worktreeResult.message };
171
+ };
172
+ var ProcessExecutionError = class extends ProcessError {
173
+ constructor(command2, exitCode) {
174
+ super(`Command '${command2}' failed with exit code ${exitCode}`, exitCode);
175
+ this.name = "ProcessExecutionError";
83
176
  }
84
- const worktreePath = worktreeResult.path;
85
- const shell = process.env.SHELL || "/bin/sh";
86
- return new Promise((resolve) => {
87
- const childProcess = spawn(shell, [], {
88
- cwd: worktreePath,
177
+ };
178
+ var ProcessSignalError = class extends ProcessError {
179
+ constructor(signal) {
180
+ const exitCode = 128 + (signal === "SIGTERM" ? 15 : 1);
181
+ super(`Command terminated by signal: ${signal}`, exitCode);
182
+ this.name = "ProcessSignalError";
183
+ }
184
+ };
185
+ var ProcessSpawnError = class extends ProcessError {
186
+ constructor(command2, details) {
187
+ super(`Error executing command '${command2}': ${details}`);
188
+ this.name = "ProcessSpawnError";
189
+ }
190
+ };
191
+
192
+ // src/core/process/spawn.ts
193
+ async function spawnProcess(config) {
194
+ return new Promise((resolve2) => {
195
+ const { command: command2, args: args2 = [], options = {} } = config;
196
+ const childProcess = nodeSpawn(command2, args2, {
89
197
  stdio: "inherit",
90
- env: {
91
- ...process.env,
92
- // Add environment variable to indicate we're in a worktree
93
- WORKTREE_NAME: worktreeName,
94
- WORKTREE_PATH: worktreePath
95
- }
198
+ ...options
96
199
  });
97
200
  childProcess.on("error", (error) => {
98
- resolve({
99
- success: false,
100
- message: `Error starting shell: ${error.message}`
101
- });
201
+ resolve2(err(new ProcessSpawnError(command2, error.message)));
102
202
  });
103
203
  childProcess.on("exit", (code, signal) => {
104
204
  if (signal) {
105
- resolve({
106
- success: false,
107
- message: `Shell terminated by signal: ${signal}`,
108
- exitCode: 128 + (signal === "SIGTERM" ? 15 : 1)
109
- });
205
+ resolve2(err(new ProcessSignalError(signal)));
110
206
  } else {
111
207
  const exitCode = code ?? 0;
112
- resolve({
113
- success: exitCode === 0,
114
- exitCode
115
- });
208
+ if (exitCode === 0) {
209
+ resolve2(ok({ exitCode }));
210
+ } else {
211
+ resolve2(err(new ProcessExecutionError(command2, exitCode)));
212
+ }
116
213
  }
117
214
  });
118
215
  });
119
216
  }
120
- async function shellHandler(args2) {
121
- if (args2.length < 1) {
122
- console.error("Usage: phantom shell <worktree-name>");
123
- exit2(1);
124
- }
125
- const worktreeName = args2[0];
126
- const worktreeResult = await whereWorktree(worktreeName);
127
- if (!worktreeResult.success) {
128
- console.error(worktreeResult.message);
129
- exit2(1);
130
- }
131
- console.log(`Entering worktree '${worktreeName}' at ${worktreeResult.path}`);
132
- console.log("Type 'exit' to return to your original directory\n");
133
- const result = await shellInWorktree(worktreeName);
134
- if (!result.success) {
135
- if (result.message) {
136
- console.error(result.message);
217
+
218
+ // src/core/process/exec.ts
219
+ async function execInWorktree(gitRoot, worktreeName, command2) {
220
+ const validation = await validateWorktreeExists(gitRoot, worktreeName);
221
+ if (!validation.exists) {
222
+ return err(new WorktreeNotFoundError(worktreeName));
223
+ }
224
+ const worktreePath = validation.path;
225
+ const [cmd, ...args2] = command2;
226
+ return spawnProcess({
227
+ command: cmd,
228
+ args: args2,
229
+ options: {
230
+ cwd: worktreePath
137
231
  }
138
- exit2(result.exitCode ?? 1);
232
+ });
233
+ }
234
+
235
+ // src/core/process/shell.ts
236
+ async function shellInWorktree(gitRoot, worktreeName) {
237
+ const validation = await validateWorktreeExists(gitRoot, worktreeName);
238
+ if (!validation.exists) {
239
+ return err(new WorktreeNotFoundError(worktreeName));
139
240
  }
140
- exit2(result.exitCode ?? 0);
241
+ const worktreePath = validation.path;
242
+ const shell = process.env.SHELL || "/bin/sh";
243
+ return spawnProcess({
244
+ command: shell,
245
+ args: [],
246
+ options: {
247
+ cwd: worktreePath,
248
+ env: {
249
+ ...process.env,
250
+ PHANTOM: "1",
251
+ PHANTOM_NAME: worktreeName,
252
+ PHANTOM_PATH: worktreePath
253
+ }
254
+ }
255
+ });
141
256
  }
142
257
 
143
- // src/commands/create.ts
144
- async function createWorktree(name) {
145
- if (!name) {
146
- return { success: false, message: "Error: worktree name required" };
258
+ // src/core/worktree/attach.ts
259
+ import { existsSync } from "node:fs";
260
+
261
+ // src/core/git/libs/attach-worktree.ts
262
+ async function attachWorktree(gitRoot, worktreePath, branchName) {
263
+ try {
264
+ await executeGitCommand(["worktree", "add", worktreePath, branchName], {
265
+ cwd: gitRoot
266
+ });
267
+ return ok(void 0);
268
+ } catch (error) {
269
+ return err(
270
+ error instanceof Error ? error : new Error(`Failed to attach worktree: ${String(error)}`)
271
+ );
147
272
  }
273
+ }
274
+
275
+ // src/core/git/libs/branch-exists.ts
276
+ async function branchExists(gitRoot, branchName) {
148
277
  try {
149
- const gitRoot = await getGitRoot();
150
- const worktreesPath = join2(gitRoot, ".git", "phantom", "worktrees");
151
- const worktreePath = join2(worktreesPath, name);
152
- try {
153
- await access2(worktreesPath);
154
- } catch {
155
- await mkdir(worktreesPath, { recursive: true });
278
+ await executeGitCommand(
279
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
280
+ { cwd: gitRoot }
281
+ );
282
+ return ok(true);
283
+ } catch (error) {
284
+ if (error && typeof error === "object" && "code" in error) {
285
+ const execError = error;
286
+ if (execError.code === 1) {
287
+ return ok(false);
288
+ }
156
289
  }
157
- try {
158
- await access2(worktreePath);
159
- return {
160
- success: false,
161
- message: `Error: worktree '${name}' already exists`
162
- };
163
- } catch {
290
+ return err(
291
+ new Error(
292
+ `Failed to check branch existence: ${error instanceof Error ? error.message : String(error)}`
293
+ )
294
+ );
295
+ }
296
+ }
297
+
298
+ // src/core/worktree/attach.ts
299
+ async function attachWorktreeCore(gitRoot, name) {
300
+ const validation = validateWorktreeName(name);
301
+ if (isErr(validation)) {
302
+ return validation;
303
+ }
304
+ const worktreePath = getWorktreePath(gitRoot, name);
305
+ if (existsSync(worktreePath)) {
306
+ return err(new WorktreeAlreadyExistsError(name));
307
+ }
308
+ const branchCheckResult = await branchExists(gitRoot, name);
309
+ if (isErr(branchCheckResult)) {
310
+ return err(branchCheckResult.error);
311
+ }
312
+ if (!branchCheckResult.value) {
313
+ return err(new BranchNotFoundError(name));
314
+ }
315
+ const attachResult = await attachWorktree(gitRoot, worktreePath, name);
316
+ if (isErr(attachResult)) {
317
+ return err(attachResult.error);
318
+ }
319
+ return ok(worktreePath);
320
+ }
321
+
322
+ // src/cli/output.ts
323
+ var output = {
324
+ log: (message) => {
325
+ console.log(message);
326
+ },
327
+ error: (message) => {
328
+ console.error(message);
329
+ },
330
+ table: (data) => {
331
+ console.table(data);
332
+ },
333
+ processOutput: (proc) => {
334
+ proc.stdout?.pipe(process.stdout);
335
+ proc.stderr?.pipe(process.stderr);
336
+ }
337
+ };
338
+
339
+ // src/cli/errors.ts
340
+ var exitCodes = {
341
+ success: 0,
342
+ generalError: 1,
343
+ notFound: 2,
344
+ validationError: 3
345
+ };
346
+ function exitWithSuccess() {
347
+ process.exit(exitCodes.success);
348
+ }
349
+ function exitWithError(message, exitCode = exitCodes.generalError) {
350
+ output.error(message);
351
+ process.exit(exitCode);
352
+ }
353
+
354
+ // src/cli/handlers/attach.ts
355
+ async function attachHandler(args2) {
356
+ const { positionals, values } = parseArgs({
357
+ args: args2,
358
+ strict: true,
359
+ allowPositionals: true,
360
+ options: {
361
+ shell: {
362
+ type: "boolean",
363
+ short: "s"
364
+ },
365
+ exec: {
366
+ type: "string",
367
+ short: "e"
368
+ }
164
369
  }
370
+ });
371
+ if (positionals.length === 0) {
372
+ exitWithError(
373
+ "Missing required argument: branch name",
374
+ exitCodes.validationError
375
+ );
376
+ }
377
+ const [branchName] = positionals;
378
+ if (values.shell && values.exec) {
379
+ exitWithError(
380
+ "Cannot use both --shell and --exec options",
381
+ exitCodes.validationError
382
+ );
383
+ }
384
+ const gitRoot = await getGitRoot();
385
+ const result = await attachWorktreeCore(gitRoot, branchName);
386
+ if (isErr(result)) {
387
+ const error = result.error;
388
+ if (error instanceof WorktreeAlreadyExistsError) {
389
+ exitWithError(error.message, exitCodes.validationError);
390
+ }
391
+ if (error instanceof BranchNotFoundError) {
392
+ exitWithError(error.message, exitCodes.notFound);
393
+ }
394
+ exitWithError(error.message, exitCodes.generalError);
395
+ }
396
+ const worktreePath = result.value;
397
+ output.log(`Attached phantom: ${branchName}`);
398
+ if (values.shell) {
399
+ const shellResult = await shellInWorktree(gitRoot, branchName);
400
+ if (isErr(shellResult)) {
401
+ exitWithError(shellResult.error.message, exitCodes.generalError);
402
+ }
403
+ } else if (values.exec) {
404
+ const execResult = await execInWorktree(
405
+ gitRoot,
406
+ branchName,
407
+ values.exec.split(" ")
408
+ );
409
+ if (isErr(execResult)) {
410
+ exitWithError(execResult.error.message, exitCodes.generalError);
411
+ }
412
+ }
413
+ }
414
+
415
+ // src/cli/handlers/create.ts
416
+ import { parseArgs as parseArgs2 } from "node:util";
417
+
418
+ // src/core/worktree/create.ts
419
+ import fs2 from "node:fs/promises";
420
+
421
+ // src/core/git/libs/add-worktree.ts
422
+ async function addWorktree(options) {
423
+ const { path, branch, commitish = "HEAD" } = options;
424
+ await executeGitCommand(["worktree", "add", path, "-b", branch, commitish]);
425
+ }
426
+
427
+ // src/core/worktree/create.ts
428
+ async function createWorktree(gitRoot, name, options = {}) {
429
+ const nameValidation = validateWorktreeName(name);
430
+ if (isErr(nameValidation)) {
431
+ return nameValidation;
432
+ }
433
+ const { branch = name, commitish = "HEAD" } = options;
434
+ const worktreesPath = getPhantomDirectory(gitRoot);
435
+ const worktreePath = getWorktreePath(gitRoot, name);
436
+ try {
437
+ await fs2.access(worktreesPath);
438
+ } catch {
439
+ await fs2.mkdir(worktreesPath, { recursive: true });
440
+ }
441
+ const validation = await validateWorktreeDoesNotExist(gitRoot, name);
442
+ if (validation.exists) {
443
+ return err(new WorktreeAlreadyExistsError(name));
444
+ }
445
+ try {
165
446
  await addWorktree({
166
447
  path: worktreePath,
167
- branch: name,
168
- commitish: "HEAD"
448
+ branch,
449
+ commitish
169
450
  });
170
- return {
171
- success: true,
451
+ return ok({
172
452
  message: `Created worktree '${name}' at ${worktreePath}`,
173
453
  path: worktreePath
174
- };
454
+ });
175
455
  } catch (error) {
176
456
  const errorMessage = error instanceof Error ? error.message : String(error);
177
- return {
178
- success: false,
179
- message: `Error creating worktree: ${errorMessage}`
180
- };
457
+ return err(new GitOperationError("worktree add", errorMessage));
181
458
  }
182
459
  }
460
+
461
+ // src/cli/handlers/create.ts
183
462
  async function createHandler(args2) {
184
- const name = args2[0];
185
- const openShell = args2.includes("--shell");
186
- const result = await createWorktree(name);
187
- if (!result.success) {
188
- console.error(result.message);
189
- exit3(1);
190
- }
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);
463
+ const { values, positionals } = parseArgs2({
464
+ args: args2,
465
+ options: {
466
+ shell: {
467
+ type: "boolean",
468
+ short: "s"
469
+ },
470
+ exec: {
471
+ type: "string",
472
+ short: "x"
200
473
  }
201
- exit3(shellResult.exitCode ?? 1);
202
- }
203
- exit3(shellResult.exitCode ?? 0);
474
+ },
475
+ strict: true,
476
+ allowPositionals: true
477
+ });
478
+ if (positionals.length === 0) {
479
+ exitWithError(
480
+ "Please provide a name for the new worktree",
481
+ exitCodes.validationError
482
+ );
204
483
  }
205
- }
206
-
207
- // src/commands/delete.ts
208
- import { exec as exec3 } from "node:child_process";
209
- import { access as access3 } from "node:fs/promises";
210
- import { join as join3 } from "node:path";
211
- import { exit as exit4 } from "node:process";
212
- import { promisify as promisify3 } from "node:util";
213
- var execAsync3 = promisify3(exec3);
214
- async function deleteWorktree(name, options = {}) {
215
- if (!name) {
216
- return { success: false, message: "Error: worktree name required" };
484
+ const worktreeName = positionals[0];
485
+ const openShell = values.shell ?? false;
486
+ const execCommand = values.exec;
487
+ if (openShell && execCommand) {
488
+ exitWithError(
489
+ "Cannot use --shell and --exec together",
490
+ exitCodes.validationError
491
+ );
217
492
  }
218
- const { force = false } = options;
219
493
  try {
220
494
  const gitRoot = await getGitRoot();
221
- const worktreesPath = join3(gitRoot, ".git", "phantom", "worktrees");
222
- const worktreePath = join3(worktreesPath, name);
223
- try {
224
- await access3(worktreePath);
225
- } catch {
226
- return {
227
- success: false,
228
- message: `Error: Worktree '${name}' does not exist`
229
- };
495
+ const result = await createWorktree(gitRoot, worktreeName);
496
+ if (isErr(result)) {
497
+ const exitCode = result.error instanceof WorktreeAlreadyExistsError ? exitCodes.validationError : exitCodes.generalError;
498
+ exitWithError(result.error.message, exitCode);
230
499
  }
231
- let hasUncommittedChanges = false;
232
- let changedFiles = 0;
233
- try {
234
- const { stdout } = await execAsync3("git status --porcelain", {
235
- cwd: worktreePath
236
- });
237
- const changes = stdout.trim();
238
- if (changes) {
239
- hasUncommittedChanges = true;
240
- changedFiles = changes.split("\n").length;
500
+ output.log(result.value.message);
501
+ if (execCommand && isOk(result)) {
502
+ output.log(
503
+ `
504
+ Executing command in worktree '${worktreeName}': ${execCommand}`
505
+ );
506
+ const shell = process.env.SHELL || "/bin/sh";
507
+ const execResult = await execInWorktree(gitRoot, worktreeName, [
508
+ shell,
509
+ "-c",
510
+ execCommand
511
+ ]);
512
+ if (isErr(execResult)) {
513
+ output.error(execResult.error.message);
514
+ const exitCode = "exitCode" in execResult.error ? execResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
515
+ exitWithError("", exitCode);
241
516
  }
242
- } catch {
243
- hasUncommittedChanges = false;
517
+ process.exit(execResult.value.exitCode ?? 0);
518
+ }
519
+ if (openShell && isOk(result)) {
520
+ output.log(
521
+ `
522
+ Entering worktree '${worktreeName}' at ${result.value.path}`
523
+ );
524
+ output.log("Type 'exit' to return to your original directory\n");
525
+ const shellResult = await shellInWorktree(gitRoot, worktreeName);
526
+ if (isErr(shellResult)) {
527
+ output.error(shellResult.error.message);
528
+ const exitCode = "exitCode" in shellResult.error ? shellResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError;
529
+ exitWithError("", exitCode);
530
+ }
531
+ process.exit(shellResult.value.exitCode ?? 0);
244
532
  }
245
- if (hasUncommittedChanges && !force) {
533
+ exitWithSuccess();
534
+ } catch (error) {
535
+ exitWithError(
536
+ error instanceof Error ? error.message : String(error),
537
+ exitCodes.generalError
538
+ );
539
+ }
540
+ }
541
+
542
+ // src/cli/handlers/delete.ts
543
+ import { parseArgs as parseArgs3 } from "node:util";
544
+
545
+ // src/core/worktree/delete.ts
546
+ async function getWorktreeStatus(worktreePath) {
547
+ try {
548
+ const { stdout } = await executeGitCommandInDirectory(worktreePath, [
549
+ "status",
550
+ "--porcelain"
551
+ ]);
552
+ if (stdout) {
246
553
  return {
247
- success: false,
248
- message: `Error: Worktree '${name}' has uncommitted changes (${changedFiles} files). Use --force to delete anyway.`,
249
554
  hasUncommittedChanges: true,
250
- changedFiles
555
+ changedFiles: stdout.split("\n").length
251
556
  };
252
557
  }
558
+ } catch {
559
+ }
560
+ return {
561
+ hasUncommittedChanges: false,
562
+ changedFiles: 0
563
+ };
564
+ }
565
+ async function removeWorktree(gitRoot, worktreePath, force = false) {
566
+ try {
567
+ await executeGitCommand(["worktree", "remove", worktreePath], {
568
+ cwd: gitRoot
569
+ });
570
+ } catch (error) {
253
571
  try {
254
- await execAsync3(`git worktree remove "${worktreePath}"`, {
255
- cwd: gitRoot
256
- });
257
- } catch (error) {
258
- try {
259
- await execAsync3(`git worktree remove --force "${worktreePath}"`, {
260
- cwd: gitRoot
261
- });
262
- } catch {
263
- return {
264
- success: false,
265
- message: `Error: Failed to remove worktree '${name}'`
266
- };
267
- }
268
- }
269
- const branchName = `phantom/worktrees/${name}`;
270
- try {
271
- await execAsync3(`git branch -D "${branchName}"`, {
572
+ await executeGitCommand(["worktree", "remove", "--force", worktreePath], {
272
573
  cwd: gitRoot
273
574
  });
274
575
  } catch {
576
+ throw new Error("Failed to remove worktree");
275
577
  }
276
- let message = `Deleted worktree '${name}' and its branch '${branchName}'`;
277
- if (hasUncommittedChanges) {
278
- message = `Warning: Worktree '${name}' had uncommitted changes (${changedFiles} files)
578
+ }
579
+ }
580
+ async function deleteBranch(gitRoot, branchName) {
581
+ try {
582
+ await executeGitCommand(["branch", "-D", branchName], { cwd: gitRoot });
583
+ return ok(true);
584
+ } catch (error) {
585
+ const errorMessage = error instanceof Error ? error.message : String(error);
586
+ return err(new GitOperationError("branch delete", errorMessage));
587
+ }
588
+ }
589
+ async function deleteWorktree(gitRoot, name, options = {}) {
590
+ const { force = false } = options;
591
+ const validation = await validateWorktreeExists(gitRoot, name);
592
+ if (!validation.exists) {
593
+ return err(new WorktreeNotFoundError(name));
594
+ }
595
+ const worktreePath = validation.path;
596
+ const status = await getWorktreeStatus(worktreePath);
597
+ if (status.hasUncommittedChanges && !force) {
598
+ return err(
599
+ new WorktreeError(
600
+ `Worktree '${name}' has uncommitted changes (${status.changedFiles} files). Use --force to delete anyway.`
601
+ )
602
+ );
603
+ }
604
+ try {
605
+ await removeWorktree(gitRoot, worktreePath, force);
606
+ const branchName = name;
607
+ const branchResult = await deleteBranch(gitRoot, branchName);
608
+ let message;
609
+ if (isOk(branchResult)) {
610
+ message = `Deleted worktree '${name}' and its branch '${branchName}'`;
611
+ } else {
612
+ message = `Deleted worktree '${name}'`;
613
+ message += `
614
+ Note: Branch '${branchName}' could not be deleted: ${branchResult.error.message}`;
615
+ }
616
+ if (status.hasUncommittedChanges) {
617
+ message = `Warning: Worktree '${name}' had uncommitted changes (${status.changedFiles} files)
279
618
  ${message}`;
280
619
  }
281
- return {
282
- success: true,
620
+ return ok({
283
621
  message,
284
- hasUncommittedChanges,
285
- changedFiles: hasUncommittedChanges ? changedFiles : void 0
286
- };
622
+ hasUncommittedChanges: status.hasUncommittedChanges,
623
+ changedFiles: status.hasUncommittedChanges ? status.changedFiles : void 0
624
+ });
287
625
  } catch (error) {
288
626
  const errorMessage = error instanceof Error ? error.message : String(error);
289
- return {
290
- success: false,
291
- message: `Error deleting worktree: ${errorMessage}`
292
- };
293
- }
294
- }
295
- async function deleteHandler(args2) {
296
- const forceIndex = args2.indexOf("--force");
297
- const force = forceIndex !== -1;
298
- const filteredArgs = args2.filter((arg) => arg !== "--force");
299
- const name = filteredArgs[0];
300
- const result = await deleteWorktree(name, { force });
301
- if (!result.success) {
302
- console.error(result.message);
303
- exit4(1);
627
+ return err(new GitOperationError("worktree remove", errorMessage));
304
628
  }
305
- console.log(result.message);
306
629
  }
307
630
 
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
- });
631
+ // src/cli/handlers/delete.ts
632
+ async function deleteHandler(args2) {
633
+ const { values, positionals } = parseArgs3({
634
+ args: args2,
635
+ options: {
636
+ force: {
637
+ type: "boolean",
638
+ short: "f"
348
639
  }
349
- });
640
+ },
641
+ strict: true,
642
+ allowPositionals: true
350
643
  });
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);
644
+ if (positionals.length === 0) {
645
+ exitWithError(
646
+ "Please provide a worktree name to delete",
647
+ exitCodes.validationError
648
+ );
649
+ }
650
+ const worktreeName = positionals[0];
651
+ const forceDelete = values.force ?? false;
652
+ try {
653
+ const gitRoot = await getGitRoot();
654
+ const result = await deleteWorktree(gitRoot, worktreeName, {
655
+ force: forceDelete
656
+ });
657
+ if (isErr(result)) {
658
+ const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.validationError : result.error instanceof WorktreeError && result.error.message.includes("uncommitted changes") ? exitCodes.validationError : exitCodes.generalError;
659
+ exitWithError(result.error.message, exitCode);
363
660
  }
364
- exit5(result.exitCode ?? 1);
661
+ output.log(result.value.message);
662
+ exitWithSuccess();
663
+ } catch (error) {
664
+ exitWithError(
665
+ error instanceof Error ? error.message : String(error),
666
+ exitCodes.generalError
667
+ );
365
668
  }
366
- exit5(result.exitCode ?? 0);
367
669
  }
368
670
 
369
- // src/commands/list.ts
370
- import { exec as exec4 } from "node:child_process";
371
- import { access as access4, readdir } from "node:fs/promises";
372
- import { join as join4 } from "node:path";
373
- import { promisify as promisify4 } from "node:util";
374
- var execAsync4 = promisify4(exec4);
375
- async function listWorktrees() {
671
+ // src/cli/handlers/exec.ts
672
+ import { parseArgs as parseArgs4 } from "node:util";
673
+ async function execHandler(args2) {
674
+ const { positionals } = parseArgs4({
675
+ args: args2,
676
+ options: {},
677
+ strict: true,
678
+ allowPositionals: true
679
+ });
680
+ if (positionals.length < 2) {
681
+ exitWithError(
682
+ "Usage: phantom exec <worktree-name> <command> [args...]",
683
+ exitCodes.validationError
684
+ );
685
+ }
686
+ const [worktreeName, ...commandArgs] = positionals;
376
687
  try {
377
688
  const gitRoot = await getGitRoot();
378
- const worktreesPath = join4(gitRoot, ".git", "phantom", "worktrees");
379
- try {
380
- await access4(worktreesPath);
381
- } catch {
382
- return {
383
- success: true,
384
- worktrees: [],
385
- message: "No worktrees found (worktrees directory doesn't exist)"
386
- };
689
+ const result = await execInWorktree(gitRoot, worktreeName, commandArgs);
690
+ if (isErr(result)) {
691
+ const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.notFound : result.error.exitCode || exitCodes.generalError;
692
+ exitWithError(result.error.message, exitCode);
387
693
  }
388
- let worktreeNames;
389
- try {
390
- const entries = await readdir(worktreesPath);
391
- const validEntries = await Promise.all(
392
- entries.map(async (entry) => {
393
- try {
394
- const entryPath = join4(worktreesPath, entry);
395
- await access4(entryPath);
396
- return entry;
397
- } catch {
398
- return null;
399
- }
400
- })
401
- );
402
- worktreeNames = validEntries.filter(
403
- (entry) => entry !== null
404
- );
405
- } catch {
406
- return {
407
- success: true,
408
- worktrees: [],
409
- message: "No worktrees found (unable to read worktrees directory)"
694
+ process.exit(result.value.exitCode);
695
+ } catch (error) {
696
+ exitWithError(
697
+ error instanceof Error ? error.message : String(error),
698
+ exitCodes.generalError
699
+ );
700
+ }
701
+ }
702
+
703
+ // src/cli/handlers/list.ts
704
+ import { parseArgs as parseArgs5 } from "node:util";
705
+
706
+ // src/core/git/libs/list-worktrees.ts
707
+ async function listWorktrees(gitRoot) {
708
+ const { stdout } = await executeGitCommand([
709
+ "worktree",
710
+ "list",
711
+ "--porcelain"
712
+ ]);
713
+ const worktrees = [];
714
+ let currentWorktree = {};
715
+ const lines = stdout.split("\n").filter((line) => line.length > 0);
716
+ for (const line of lines) {
717
+ if (line.startsWith("worktree ")) {
718
+ if (currentWorktree.path) {
719
+ worktrees.push(currentWorktree);
720
+ }
721
+ currentWorktree = {
722
+ path: line.substring("worktree ".length),
723
+ isLocked: false,
724
+ isPrunable: false
410
725
  };
726
+ } else if (line.startsWith("HEAD ")) {
727
+ currentWorktree.head = line.substring("HEAD ".length);
728
+ } else if (line.startsWith("branch ")) {
729
+ const fullBranch = line.substring("branch ".length);
730
+ currentWorktree.branch = fullBranch.startsWith("refs/heads/") ? fullBranch.substring("refs/heads/".length) : fullBranch;
731
+ } else if (line === "detached") {
732
+ currentWorktree.branch = "(detached HEAD)";
733
+ } else if (line === "locked") {
734
+ currentWorktree.isLocked = true;
735
+ } else if (line === "prunable") {
736
+ currentWorktree.isPrunable = true;
411
737
  }
412
- if (worktreeNames.length === 0) {
413
- return {
414
- success: true,
738
+ }
739
+ if (currentWorktree.path) {
740
+ worktrees.push(currentWorktree);
741
+ }
742
+ return worktrees;
743
+ }
744
+
745
+ // src/core/worktree/list.ts
746
+ async function getWorktreeStatus2(worktreePath) {
747
+ try {
748
+ const { stdout } = await executeGitCommandInDirectory(worktreePath, [
749
+ "status",
750
+ "--porcelain"
751
+ ]);
752
+ return !stdout;
753
+ } catch {
754
+ return true;
755
+ }
756
+ }
757
+ async function listWorktrees2(gitRoot) {
758
+ try {
759
+ const gitWorktrees = await listWorktrees(gitRoot);
760
+ const phantomDir = getPhantomDirectory(gitRoot);
761
+ const phantomWorktrees = gitWorktrees.filter(
762
+ (worktree) => worktree.path.startsWith(phantomDir)
763
+ );
764
+ if (phantomWorktrees.length === 0) {
765
+ return ok({
415
766
  worktrees: [],
416
767
  message: "No worktrees found"
417
- };
768
+ });
418
769
  }
419
770
  const worktrees = await Promise.all(
420
- worktreeNames.map(async (name) => {
421
- const worktreePath = join4(worktreesPath, name);
422
- let branch = "unknown";
423
- try {
424
- const { stdout } = await execAsync4("git branch --show-current", {
425
- cwd: worktreePath
426
- });
427
- branch = stdout.trim() || "detached HEAD";
428
- } catch {
429
- branch = "unknown";
430
- }
431
- let status = "clean";
432
- let changedFiles;
433
- try {
434
- const { stdout } = await execAsync4("git status --porcelain", {
435
- cwd: worktreePath
436
- });
437
- const changes = stdout.trim();
438
- if (changes) {
439
- status = "dirty";
440
- changedFiles = changes.split("\n").length;
441
- }
442
- } catch {
443
- status = "clean";
444
- }
771
+ phantomWorktrees.map(async (gitWorktree) => {
772
+ const name = gitWorktree.path.substring(phantomDir.length + 1);
773
+ const isClean = await getWorktreeStatus2(gitWorktree.path);
445
774
  return {
446
775
  name,
447
- branch,
448
- status,
449
- changedFiles
776
+ path: gitWorktree.path,
777
+ branch: gitWorktree.branch || "(detached HEAD)",
778
+ isClean
450
779
  };
451
780
  })
452
781
  );
453
- return {
454
- success: true,
782
+ return ok({
455
783
  worktrees
456
- };
784
+ });
457
785
  } catch (error) {
458
786
  const errorMessage = error instanceof Error ? error.message : String(error);
459
- return {
460
- success: false,
461
- message: `Error listing worktrees: ${errorMessage}`
462
- };
787
+ throw new Error(`Failed to list worktrees: ${errorMessage}`);
463
788
  }
464
789
  }
465
- async function listHandler() {
466
- const result = await listWorktrees();
467
- if (!result.success) {
468
- console.error(result.message);
469
- return;
790
+
791
+ // src/cli/handlers/list.ts
792
+ async function listHandler(args2 = []) {
793
+ parseArgs5({
794
+ args: args2,
795
+ options: {},
796
+ strict: true,
797
+ allowPositionals: false
798
+ });
799
+ try {
800
+ const gitRoot = await getGitRoot();
801
+ const result = await listWorktrees2(gitRoot);
802
+ if (isErr(result)) {
803
+ exitWithError("Failed to list worktrees", exitCodes.generalError);
804
+ }
805
+ const { worktrees, message } = result.value;
806
+ if (worktrees.length === 0) {
807
+ output.log(message || "No worktrees found.");
808
+ process.exit(exitCodes.success);
809
+ }
810
+ const maxNameLength = Math.max(...worktrees.map((wt) => wt.name.length));
811
+ for (const worktree of worktrees) {
812
+ const paddedName = worktree.name.padEnd(maxNameLength + 2);
813
+ const branchInfo = worktree.branch ? `(${worktree.branch})` : "";
814
+ const status = !worktree.isClean ? " [dirty]" : "";
815
+ output.log(`${paddedName} ${branchInfo}${status}`);
816
+ }
817
+ process.exit(exitCodes.success);
818
+ } catch (error) {
819
+ exitWithError(
820
+ error instanceof Error ? error.message : String(error),
821
+ exitCodes.generalError
822
+ );
470
823
  }
471
- if (!result.worktrees || result.worktrees.length === 0) {
472
- console.log(result.message || "No worktrees found");
473
- return;
824
+ }
825
+
826
+ // src/cli/handlers/shell.ts
827
+ import { parseArgs as parseArgs6 } from "node:util";
828
+ async function shellHandler(args2) {
829
+ const { positionals } = parseArgs6({
830
+ args: args2,
831
+ options: {},
832
+ strict: true,
833
+ allowPositionals: true
834
+ });
835
+ if (positionals.length === 0) {
836
+ exitWithError(
837
+ "Usage: phantom shell <worktree-name>",
838
+ exitCodes.validationError
839
+ );
474
840
  }
475
- console.log("Worktrees:");
476
- for (const worktree of result.worktrees) {
477
- const statusText = worktree.status === "clean" ? "[clean]" : `[dirty: ${worktree.changedFiles} files]`;
478
- console.log(
479
- ` ${worktree.name.padEnd(20)} (branch: ${worktree.branch.padEnd(20)}) ${statusText}`
841
+ const worktreeName = positionals[0];
842
+ try {
843
+ const gitRoot = await getGitRoot();
844
+ const validation = await validateWorktreeExists(gitRoot, worktreeName);
845
+ if (!validation.exists) {
846
+ exitWithError(
847
+ validation.message || `Worktree '${worktreeName}' not found`,
848
+ exitCodes.generalError
849
+ );
850
+ }
851
+ output.log(`Entering worktree '${worktreeName}' at ${validation.path}`);
852
+ output.log("Type 'exit' to return to your original directory\n");
853
+ const result = await shellInWorktree(gitRoot, worktreeName);
854
+ if (isErr(result)) {
855
+ const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.notFound : result.error.exitCode || exitCodes.generalError;
856
+ exitWithError(result.error.message, exitCode);
857
+ }
858
+ process.exit(result.value.exitCode);
859
+ } catch (error) {
860
+ exitWithError(
861
+ error instanceof Error ? error.message : String(error),
862
+ exitCodes.generalError
480
863
  );
481
864
  }
482
- console.log(`
483
- Total: ${result.worktrees.length} worktrees`);
484
865
  }
485
866
 
867
+ // src/cli/handlers/version.ts
868
+ import { parseArgs as parseArgs7 } from "node:util";
869
+
486
870
  // package.json
487
871
  var package_default = {
488
872
  name: "@aku11i/phantom",
489
873
  packageManager: "pnpm@10.8.1",
490
- version: "0.3.0",
874
+ version: "0.5.0",
491
875
  description: "A powerful CLI tool for managing Git worktrees for parallel development",
492
876
  keywords: [
493
877
  "git",
@@ -516,12 +900,12 @@ var package_default = {
516
900
  start: "node ./src/bin/phantom.ts",
517
901
  phantom: "node ./src/bin/phantom.ts",
518
902
  build: "node build.ts",
519
- "type-check": "tsc --noEmit",
903
+ typecheck: "tsgo --noEmit",
520
904
  test: "node --test --experimental-strip-types --experimental-test-module-mocks src/**/*.test.ts",
521
905
  lint: "biome check .",
522
906
  fix: "biome check --write .",
523
- ready: "pnpm fix && pnpm type-check && pnpm test",
524
- "ready:check": "pnpm lint && pnpm type-check && pnpm test",
907
+ ready: "pnpm fix && pnpm typecheck && pnpm test",
908
+ "ready:check": "pnpm lint && pnpm typecheck && pnpm test",
525
909
  prepublishOnly: "pnpm ready:check && pnpm build"
526
910
  },
527
911
  engines: {
@@ -535,27 +919,84 @@ var package_default = {
535
919
  devDependencies: {
536
920
  "@biomejs/biome": "^1.9.4",
537
921
  "@types/node": "^22.15.29",
922
+ "@typescript/native-preview": "7.0.0-dev.20250602.1",
538
923
  esbuild: "^0.25.5",
539
924
  typescript: "^5.8.3"
540
925
  }
541
926
  };
542
927
 
543
- // src/commands/version.ts
928
+ // src/core/version.ts
544
929
  function getVersion() {
545
930
  return package_default.version;
546
931
  }
547
- function versionHandler() {
932
+
933
+ // src/cli/handlers/version.ts
934
+ function versionHandler(args2 = []) {
935
+ parseArgs7({
936
+ args: args2,
937
+ options: {},
938
+ strict: true,
939
+ allowPositionals: false
940
+ });
548
941
  const version = getVersion();
549
- console.log(`Phantom v${version}`);
942
+ output.log(`Phantom v${version}`);
943
+ exitWithSuccess();
944
+ }
945
+
946
+ // src/cli/handlers/where.ts
947
+ import { parseArgs as parseArgs8 } from "node:util";
948
+
949
+ // src/core/worktree/where.ts
950
+ async function whereWorktree(gitRoot, name) {
951
+ const validation = await validateWorktreeExists(gitRoot, name);
952
+ if (!validation.exists) {
953
+ return err(new WorktreeNotFoundError(name));
954
+ }
955
+ return ok({
956
+ path: validation.path
957
+ });
958
+ }
959
+
960
+ // src/cli/handlers/where.ts
961
+ async function whereHandler(args2) {
962
+ const { positionals } = parseArgs8({
963
+ args: args2,
964
+ options: {},
965
+ strict: true,
966
+ allowPositionals: true
967
+ });
968
+ if (positionals.length === 0) {
969
+ exitWithError("Please provide a worktree name", exitCodes.validationError);
970
+ }
971
+ const worktreeName = positionals[0];
972
+ try {
973
+ const gitRoot = await getGitRoot();
974
+ const result = await whereWorktree(gitRoot, worktreeName);
975
+ if (isErr(result)) {
976
+ exitWithError(result.error.message, exitCodes.notFound);
977
+ }
978
+ output.log(result.value.path);
979
+ exitWithSuccess();
980
+ } catch (error) {
981
+ exitWithError(
982
+ error instanceof Error ? error.message : String(error),
983
+ exitCodes.generalError
984
+ );
985
+ }
550
986
  }
551
987
 
552
988
  // src/bin/phantom.ts
553
989
  var commands = [
554
990
  {
555
991
  name: "create",
556
- description: "Create a new worktree [--shell to open shell]",
992
+ description: "Create a new worktree [--shell | --exec <command>]",
557
993
  handler: createHandler
558
994
  },
995
+ {
996
+ name: "attach",
997
+ description: "Attach to an existing branch [--shell | --exec <command>]",
998
+ handler: attachHandler
999
+ },
559
1000
  {
560
1001
  name: "list",
561
1002
  description: "List all worktrees",
@@ -617,18 +1058,18 @@ function findCommand(args2, commands2) {
617
1058
  var args = argv.slice(2);
618
1059
  if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
619
1060
  printHelp(commands);
620
- exit6(0);
1061
+ exit(0);
621
1062
  }
622
1063
  if (args[0] === "--version" || args[0] === "-v") {
623
1064
  versionHandler();
624
- exit6(0);
1065
+ exit(0);
625
1066
  }
626
1067
  var { command, remainingArgs } = findCommand(args, commands);
627
1068
  if (!command || !command.handler) {
628
1069
  console.error(`Error: Unknown command '${args.join(" ")}'
629
1070
  `);
630
1071
  printHelp(commands);
631
- exit6(1);
1072
+ exit(1);
632
1073
  }
633
1074
  try {
634
1075
  await command.handler(remainingArgs);
@@ -637,6 +1078,6 @@ try {
637
1078
  "Error:",
638
1079
  error instanceof Error ? error.message : String(error)
639
1080
  );
640
- exit6(1);
1081
+ exit(1);
641
1082
  }
642
1083
  //# sourceMappingURL=phantom.js.map