@aku11i/phantom 1.3.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/phantom.js +279 -61
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aku11i/phantom",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A powerful CLI tool for managing Git worktrees for parallel development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"git",
|
|
@@ -31,5 +31,8 @@
|
|
|
31
31
|
"README.md",
|
|
32
32
|
"LICENSE"
|
|
33
33
|
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.0.0"
|
|
36
|
+
},
|
|
34
37
|
"dependencies": {}
|
|
35
38
|
}
|
package/phantom.js
CHANGED
|
@@ -4203,13 +4203,16 @@ var phantomConfigSchema = external_exports.object({
|
|
|
4203
4203
|
copyFiles: external_exports.array(external_exports.string()).optional(),
|
|
4204
4204
|
commands: external_exports.array(external_exports.string()).optional()
|
|
4205
4205
|
}).passthrough().optional(),
|
|
4206
|
-
|
|
4206
|
+
preDelete: external_exports.object({
|
|
4207
|
+
commands: external_exports.array(external_exports.string()).optional()
|
|
4208
|
+
}).passthrough().optional(),
|
|
4209
|
+
worktreesDirectory: external_exports.string().optional(),
|
|
4210
|
+
defaultBranch: external_exports.string().optional()
|
|
4207
4211
|
}).passthrough();
|
|
4208
4212
|
function validateConfig(config) {
|
|
4209
4213
|
const result = phantomConfigSchema.safeParse(config);
|
|
4210
4214
|
if (!result.success) {
|
|
4211
4215
|
const error = result.error;
|
|
4212
|
-
const formattedError = error.format();
|
|
4213
4216
|
const firstError = error.errors[0];
|
|
4214
4217
|
const path3 = firstError.path.join(".");
|
|
4215
4218
|
const message = path3 ? `${path3}: ${firstError.message}` : firstError.message;
|
|
@@ -4403,7 +4406,7 @@ async function fetch(options = {}) {
|
|
|
4403
4406
|
}
|
|
4404
4407
|
|
|
4405
4408
|
// ../git/src/libs/list-worktrees.ts
|
|
4406
|
-
async function listWorktrees(
|
|
4409
|
+
async function listWorktrees(_gitRoot) {
|
|
4407
4410
|
const { stdout: stdout2 } = await executeGitCommand([
|
|
4408
4411
|
"worktree",
|
|
4409
4412
|
"list",
|
|
@@ -4695,7 +4698,7 @@ async function selectWithFzf(items, options = {}) {
|
|
|
4695
4698
|
|
|
4696
4699
|
// ../core/src/worktree/validate.ts
|
|
4697
4700
|
import fs2 from "node:fs/promises";
|
|
4698
|
-
async function validateWorktreeExists(
|
|
4701
|
+
async function validateWorktreeExists(_gitRoot, worktreeDirectory, name) {
|
|
4699
4702
|
const worktreePath = getWorktreePathFromDirectory(worktreeDirectory, name);
|
|
4700
4703
|
try {
|
|
4701
4704
|
await fs2.access(worktreePath);
|
|
@@ -4704,7 +4707,7 @@ async function validateWorktreeExists(gitRoot, worktreeDirectory, name) {
|
|
|
4704
4707
|
return err(new WorktreeNotFoundError(name));
|
|
4705
4708
|
}
|
|
4706
4709
|
}
|
|
4707
|
-
async function validateWorktreeDoesNotExist(
|
|
4710
|
+
async function validateWorktreeDoesNotExist(_gitRoot, worktreeDirectory, name) {
|
|
4708
4711
|
const worktreePath = getWorktreePathFromDirectory(worktreeDirectory, name);
|
|
4709
4712
|
try {
|
|
4710
4713
|
await fs2.access(worktreePath);
|
|
@@ -4879,6 +4882,39 @@ async function createWorktree(gitRoot, worktreeDirectory, name, options, postCre
|
|
|
4879
4882
|
}
|
|
4880
4883
|
}
|
|
4881
4884
|
|
|
4885
|
+
// ../core/src/worktree/pre-delete.ts
|
|
4886
|
+
async function executePreDeleteCommands(options) {
|
|
4887
|
+
const { gitRoot, worktreesDirectory, worktreeName, commands: commands2 } = options;
|
|
4888
|
+
const executedCommands = [];
|
|
4889
|
+
for (const command2 of commands2) {
|
|
4890
|
+
console.log(`Executing pre-delete command: ${command2}`);
|
|
4891
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
4892
|
+
const cmdResult = await execInWorktree(
|
|
4893
|
+
gitRoot,
|
|
4894
|
+
worktreesDirectory,
|
|
4895
|
+
worktreeName,
|
|
4896
|
+
[shell, "-c", command2]
|
|
4897
|
+
);
|
|
4898
|
+
if (isErr(cmdResult)) {
|
|
4899
|
+
const errorMessage = cmdResult.error instanceof Error ? cmdResult.error.message : String(cmdResult.error);
|
|
4900
|
+
return err(
|
|
4901
|
+
new Error(
|
|
4902
|
+
`Failed to execute pre-delete command "${command2}": ${errorMessage}`
|
|
4903
|
+
)
|
|
4904
|
+
);
|
|
4905
|
+
}
|
|
4906
|
+
if (cmdResult.value.exitCode !== 0) {
|
|
4907
|
+
return err(
|
|
4908
|
+
new Error(
|
|
4909
|
+
`Pre-delete command failed with exit code ${cmdResult.value.exitCode}: ${command2}`
|
|
4910
|
+
)
|
|
4911
|
+
);
|
|
4912
|
+
}
|
|
4913
|
+
executedCommands.push(command2);
|
|
4914
|
+
}
|
|
4915
|
+
return ok({ executedCommands });
|
|
4916
|
+
}
|
|
4917
|
+
|
|
4882
4918
|
// ../core/src/worktree/delete.ts
|
|
4883
4919
|
async function getWorktreeChangesStatus(worktreePath) {
|
|
4884
4920
|
try {
|
|
@@ -4900,19 +4936,14 @@ async function getWorktreeChangesStatus(worktreePath) {
|
|
|
4900
4936
|
};
|
|
4901
4937
|
}
|
|
4902
4938
|
async function removeWorktree(gitRoot, worktreePath, force = false) {
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
});
|
|
4907
|
-
} catch (error) {
|
|
4908
|
-
try {
|
|
4909
|
-
await executeGitCommand(["worktree", "remove", "--force", worktreePath], {
|
|
4910
|
-
cwd: gitRoot
|
|
4911
|
-
});
|
|
4912
|
-
} catch {
|
|
4913
|
-
throw new Error("Failed to remove worktree");
|
|
4914
|
-
}
|
|
4939
|
+
const args2 = ["worktree", "remove"];
|
|
4940
|
+
if (force) {
|
|
4941
|
+
args2.push("--force");
|
|
4915
4942
|
}
|
|
4943
|
+
args2.push(worktreePath);
|
|
4944
|
+
await executeGitCommand(args2, {
|
|
4945
|
+
cwd: gitRoot
|
|
4946
|
+
});
|
|
4916
4947
|
}
|
|
4917
4948
|
async function deleteBranch(gitRoot, branchName) {
|
|
4918
4949
|
try {
|
|
@@ -4923,7 +4954,7 @@ async function deleteBranch(gitRoot, branchName) {
|
|
|
4923
4954
|
return err(new WorktreeError(`branch delete failed: ${errorMessage}`));
|
|
4924
4955
|
}
|
|
4925
4956
|
}
|
|
4926
|
-
async function deleteWorktree(gitRoot, worktreeDirectory, name, options) {
|
|
4957
|
+
async function deleteWorktree(gitRoot, worktreeDirectory, name, options, preDeleteCommands) {
|
|
4927
4958
|
const { force = false } = options || {};
|
|
4928
4959
|
const validation = await validateWorktreeExists(
|
|
4929
4960
|
gitRoot,
|
|
@@ -4942,6 +4973,18 @@ async function deleteWorktree(gitRoot, worktreeDirectory, name, options) {
|
|
|
4942
4973
|
)
|
|
4943
4974
|
);
|
|
4944
4975
|
}
|
|
4976
|
+
if (preDeleteCommands && preDeleteCommands.length > 0) {
|
|
4977
|
+
console.log("\nRunning pre-delete commands...");
|
|
4978
|
+
const preDeleteResult = await executePreDeleteCommands({
|
|
4979
|
+
gitRoot,
|
|
4980
|
+
worktreesDirectory: worktreeDirectory,
|
|
4981
|
+
worktreeName: name,
|
|
4982
|
+
commands: preDeleteCommands
|
|
4983
|
+
});
|
|
4984
|
+
if (isErr(preDeleteResult)) {
|
|
4985
|
+
return err(new WorktreeError(preDeleteResult.error.message));
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4945
4988
|
try {
|
|
4946
4989
|
await removeWorktree(gitRoot, worktreePath, force);
|
|
4947
4990
|
const branchName = name;
|
|
@@ -5231,7 +5274,6 @@ async function attachHandler(args2) {
|
|
|
5231
5274
|
}
|
|
5232
5275
|
exitWithError(error.message, exitCodes.generalError);
|
|
5233
5276
|
}
|
|
5234
|
-
const worktreePath = result.value;
|
|
5235
5277
|
output.log(`Attached phantom: ${branchName}`);
|
|
5236
5278
|
if (values.shell) {
|
|
5237
5279
|
const shellResult = await shellInWorktree(
|
|
@@ -5295,6 +5337,7 @@ complete -c phantom -n "__phantom_using_command" -a "list" -d "List all Git work
|
|
|
5295
5337
|
complete -c phantom -n "__phantom_using_command" -a "where" -d "Output the filesystem path of a specific worktree"
|
|
5296
5338
|
complete -c phantom -n "__phantom_using_command" -a "delete" -d "Delete a Git worktree (phantom)"
|
|
5297
5339
|
complete -c phantom -n "__phantom_using_command" -a "exec" -d "Execute a command in a worktree directory"
|
|
5340
|
+
complete -c phantom -n "__phantom_using_command" -a "review" -d "Review changes in a worktree with a local PR review interface (experimental)"
|
|
5298
5341
|
complete -c phantom -n "__phantom_using_command" -a "shell" -d "Open an interactive shell in a worktree directory"
|
|
5299
5342
|
complete -c phantom -n "__phantom_using_command" -a "github" -d "GitHub integration commands"
|
|
5300
5343
|
complete -c phantom -n "__phantom_using_command" -a "gh" -d "GitHub integration commands (alias)"
|
|
@@ -5340,6 +5383,11 @@ complete -c phantom -n "__phantom_using_command exec" -l tmux-vertical -d "Execu
|
|
|
5340
5383
|
complete -c phantom -n "__phantom_using_command exec" -l tmux-horizontal -d "Execute command in horizontal split pane"
|
|
5341
5384
|
complete -c phantom -n "__phantom_using_command exec" -a "(__phantom_list_worktrees)"
|
|
5342
5385
|
|
|
5386
|
+
# review command options
|
|
5387
|
+
complete -c phantom -n "__phantom_using_command review" -l fzf -d "Use fzf for interactive selection"
|
|
5388
|
+
complete -c phantom -n "__phantom_using_command review" -l base -d "Base reference for comparison" -x
|
|
5389
|
+
complete -c phantom -n "__phantom_using_command review" -a "(__phantom_list_worktrees)"
|
|
5390
|
+
|
|
5343
5391
|
# shell command options
|
|
5344
5392
|
complete -c phantom -n "__phantom_using_command shell" -l fzf -d "Use fzf for interactive selection"
|
|
5345
5393
|
complete -c phantom -n "__phantom_using_command shell" -l tmux -d "Open shell in new tmux window (-t)"
|
|
@@ -5374,6 +5422,7 @@ _phantom() {
|
|
|
5374
5422
|
'where:Output the filesystem path of a specific worktree'
|
|
5375
5423
|
'delete:Delete a Git worktree (phantom)'
|
|
5376
5424
|
'exec:Execute a command in a worktree directory'
|
|
5425
|
+
'review:Review changes in a worktree with a local PR review interface (experimental)'
|
|
5377
5426
|
'shell:Open an interactive shell in a worktree directory'
|
|
5378
5427
|
'github:GitHub integration commands'
|
|
5379
5428
|
'gh:GitHub integration commands (alias)'
|
|
@@ -5416,13 +5465,18 @@ _phantom() {
|
|
|
5416
5465
|
'--fzf[Use fzf for interactive selection]' \\
|
|
5417
5466
|
'--names[Output only phantom names (for scripts and completion)]'
|
|
5418
5467
|
;;
|
|
5419
|
-
where|delete|shell)
|
|
5468
|
+
where|delete|review|shell)
|
|
5420
5469
|
local worktrees
|
|
5421
5470
|
worktrees=(\${(f)"$(phantom list --names 2>/dev/null)"})
|
|
5422
5471
|
if [[ \${line[1]} == "where" ]]; then
|
|
5423
5472
|
_arguments \\
|
|
5424
5473
|
'--fzf[Use fzf for interactive selection]' \\
|
|
5425
5474
|
'1:worktree:(\${(q)worktrees[@]})'
|
|
5475
|
+
elif [[ \${line[1]} == "review" ]]; then
|
|
5476
|
+
_arguments \\
|
|
5477
|
+
'--fzf[Use fzf for interactive selection]' \\
|
|
5478
|
+
'--base[Base reference for comparison]:reference:' \\
|
|
5479
|
+
'1:worktree:(\${(q)worktrees[@]})'
|
|
5426
5480
|
elif [[ \${line[1]} == "shell" ]]; then
|
|
5427
5481
|
_arguments \\
|
|
5428
5482
|
'--fzf[Use fzf for interactive selection]' \\
|
|
@@ -5488,7 +5542,7 @@ _phantom_completion() {
|
|
|
5488
5542
|
local cur prev words cword
|
|
5489
5543
|
_init_completion || return
|
|
5490
5544
|
|
|
5491
|
-
local commands="create attach list where delete exec shell github gh version completion mcp"
|
|
5545
|
+
local commands="create attach list where delete exec review shell github gh version completion mcp"
|
|
5492
5546
|
local global_opts="--help --version"
|
|
5493
5547
|
|
|
5494
5548
|
if [[ \${cword} -eq 1 ]]; then
|
|
@@ -5590,6 +5644,24 @@ _phantom_completion() {
|
|
|
5590
5644
|
;;
|
|
5591
5645
|
esac
|
|
5592
5646
|
;;
|
|
5647
|
+
review)
|
|
5648
|
+
case "\${prev}" in
|
|
5649
|
+
--base)
|
|
5650
|
+
# Don't complete anything specific for base reference
|
|
5651
|
+
return 0
|
|
5652
|
+
;;
|
|
5653
|
+
*)
|
|
5654
|
+
if [[ "\${cur}" == -* ]]; then
|
|
5655
|
+
local opts="--fzf --base"
|
|
5656
|
+
COMPREPLY=( $(compgen -W "\${opts}" -- "\${cur}") )
|
|
5657
|
+
else
|
|
5658
|
+
local worktrees=$(_phantom_list_worktrees)
|
|
5659
|
+
COMPREPLY=( $(compgen -W "\${worktrees}" -- "\${cur}") )
|
|
5660
|
+
fi
|
|
5661
|
+
return 0
|
|
5662
|
+
;;
|
|
5663
|
+
esac
|
|
5664
|
+
;;
|
|
5593
5665
|
shell)
|
|
5594
5666
|
case "\${prev}" in
|
|
5595
5667
|
--tmux|-t|--tmux-vertical|--tmux-horizontal)
|
|
@@ -5931,7 +6003,8 @@ async function deleteHandler(args2) {
|
|
|
5931
6003
|
worktreeName,
|
|
5932
6004
|
{
|
|
5933
6005
|
force: forceDelete
|
|
5934
|
-
}
|
|
6006
|
+
},
|
|
6007
|
+
context.config?.preDelete?.commands
|
|
5935
6008
|
);
|
|
5936
6009
|
if (isErr(result)) {
|
|
5937
6010
|
const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.validationError : result.error instanceof WorktreeError && result.error.message.includes("uncommitted changes") ? exitCodes.validationError : exitCodes.generalError;
|
|
@@ -9526,8 +9599,21 @@ async function checkoutIssue(issue, base) {
|
|
|
9526
9599
|
}
|
|
9527
9600
|
const gitRoot = await getGitRoot();
|
|
9528
9601
|
const context = await createContext(gitRoot);
|
|
9529
|
-
const worktreeName = `
|
|
9530
|
-
const branchName = `
|
|
9602
|
+
const worktreeName = `issues/${issue.number}`;
|
|
9603
|
+
const branchName = `issues/${issue.number}`;
|
|
9604
|
+
const existsResult = await validateWorktreeExists(
|
|
9605
|
+
context.gitRoot,
|
|
9606
|
+
context.worktreesDirectory,
|
|
9607
|
+
worktreeName
|
|
9608
|
+
);
|
|
9609
|
+
if (!isErr(existsResult)) {
|
|
9610
|
+
return ok({
|
|
9611
|
+
message: `Issue #${issue.number} is already checked out`,
|
|
9612
|
+
worktree: worktreeName,
|
|
9613
|
+
path: existsResult.value.path,
|
|
9614
|
+
alreadyExists: true
|
|
9615
|
+
});
|
|
9616
|
+
}
|
|
9531
9617
|
const result = await createWorktree(
|
|
9532
9618
|
context.gitRoot,
|
|
9533
9619
|
context.worktreesDirectory,
|
|
@@ -9540,18 +9626,6 @@ async function checkoutIssue(issue, base) {
|
|
|
9540
9626
|
context.config?.postCreate?.commands
|
|
9541
9627
|
);
|
|
9542
9628
|
if (isErr(result)) {
|
|
9543
|
-
if (result.error instanceof WorktreeAlreadyExistsError) {
|
|
9544
|
-
const worktreePath = getWorktreePathFromDirectory(
|
|
9545
|
-
context.worktreesDirectory,
|
|
9546
|
-
worktreeName
|
|
9547
|
-
);
|
|
9548
|
-
return ok({
|
|
9549
|
-
message: `Worktree for issue #${issue.number} is already checked out`,
|
|
9550
|
-
worktree: worktreeName,
|
|
9551
|
-
path: worktreePath,
|
|
9552
|
-
alreadyExists: true
|
|
9553
|
-
});
|
|
9554
|
-
}
|
|
9555
9629
|
return err(result.error);
|
|
9556
9630
|
}
|
|
9557
9631
|
return ok({
|
|
@@ -9565,8 +9639,21 @@ async function checkoutIssue(issue, base) {
|
|
|
9565
9639
|
async function checkoutPullRequest(pullRequest) {
|
|
9566
9640
|
const gitRoot = await getGitRoot();
|
|
9567
9641
|
const context = await createContext(gitRoot);
|
|
9568
|
-
const worktreeName = `
|
|
9569
|
-
const localBranch = `
|
|
9642
|
+
const worktreeName = `pulls/${pullRequest.number}`;
|
|
9643
|
+
const localBranch = `pulls/${pullRequest.number}`;
|
|
9644
|
+
const existsResult = await validateWorktreeExists(
|
|
9645
|
+
context.gitRoot,
|
|
9646
|
+
context.worktreesDirectory,
|
|
9647
|
+
worktreeName
|
|
9648
|
+
);
|
|
9649
|
+
if (!isErr(existsResult)) {
|
|
9650
|
+
return ok({
|
|
9651
|
+
message: `PR #${pullRequest.number} is already checked out`,
|
|
9652
|
+
worktree: worktreeName,
|
|
9653
|
+
path: existsResult.value.path,
|
|
9654
|
+
alreadyExists: true
|
|
9655
|
+
});
|
|
9656
|
+
}
|
|
9570
9657
|
const upstream = pullRequest.isFromFork ? `origin/pull/${pullRequest.number}/head` : `origin/${pullRequest.head.ref}`;
|
|
9571
9658
|
const refspec = `${upstream.replace("origin/", "")}:${localBranch}`;
|
|
9572
9659
|
const fetchResult = await fetch({ refspec });
|
|
@@ -9595,18 +9682,6 @@ async function checkoutPullRequest(pullRequest) {
|
|
|
9595
9682
|
context.config?.postCreate?.commands
|
|
9596
9683
|
);
|
|
9597
9684
|
if (isErr(attachResult)) {
|
|
9598
|
-
if (attachResult.error instanceof WorktreeAlreadyExistsError) {
|
|
9599
|
-
const worktreePath = getWorktreePathFromDirectory(
|
|
9600
|
-
context.worktreesDirectory,
|
|
9601
|
-
worktreeName
|
|
9602
|
-
);
|
|
9603
|
-
return ok({
|
|
9604
|
-
message: `Worktree for PR #${pullRequest.number} is already checked out`,
|
|
9605
|
-
worktree: worktreeName,
|
|
9606
|
-
path: worktreePath,
|
|
9607
|
-
alreadyExists: true
|
|
9608
|
-
});
|
|
9609
|
-
}
|
|
9610
9685
|
return err(attachResult.error);
|
|
9611
9686
|
}
|
|
9612
9687
|
const message = pullRequest.isFromFork ? `Checked out PR #${pullRequest.number} from fork ${pullRequest.head.repo.full_name}` : `Checked out PR #${pullRequest.number} from branch ${pullRequest.head.ref}`;
|
|
@@ -9866,8 +9941,8 @@ var githubCheckoutHelp = {
|
|
|
9866
9941
|
}
|
|
9867
9942
|
],
|
|
9868
9943
|
notes: [
|
|
9869
|
-
"For PRs: Creates worktree named '
|
|
9870
|
-
"For Issues: Creates worktree named '
|
|
9944
|
+
"For PRs: Creates worktree named 'pulls/{number}' with the PR's branch",
|
|
9945
|
+
"For Issues: Creates worktree named 'issues/{number}' with a new branch",
|
|
9871
9946
|
"",
|
|
9872
9947
|
"Requirements:",
|
|
9873
9948
|
" - GitHub CLI (gh) must be installed",
|
|
@@ -13223,7 +13298,7 @@ var StdioServerTransport = class {
|
|
|
13223
13298
|
// ../mcp/package.json
|
|
13224
13299
|
var package_default = {
|
|
13225
13300
|
name: "@aku11i/phantom-mcp",
|
|
13226
|
-
version: "1.
|
|
13301
|
+
version: "2.1.0",
|
|
13227
13302
|
private: true,
|
|
13228
13303
|
type: "module",
|
|
13229
13304
|
main: "./src/index.ts",
|
|
@@ -13311,7 +13386,8 @@ var deleteWorktreeTool = {
|
|
|
13311
13386
|
name,
|
|
13312
13387
|
{
|
|
13313
13388
|
force
|
|
13314
|
-
}
|
|
13389
|
+
},
|
|
13390
|
+
context.config?.preDelete?.commands
|
|
13315
13391
|
);
|
|
13316
13392
|
if (!isOk(result)) {
|
|
13317
13393
|
throw new Error(result.error.message);
|
|
@@ -13501,10 +13577,97 @@ async function mcpHandler(args2 = []) {
|
|
|
13501
13577
|
}
|
|
13502
13578
|
}
|
|
13503
13579
|
|
|
13504
|
-
// src/handlers/
|
|
13580
|
+
// src/handlers/review.ts
|
|
13505
13581
|
import { parseArgs as parseArgs8 } from "node:util";
|
|
13506
|
-
async function
|
|
13582
|
+
async function reviewHandler(args2) {
|
|
13507
13583
|
const { positionals, values } = parseArgs8({
|
|
13584
|
+
args: args2,
|
|
13585
|
+
options: {
|
|
13586
|
+
fzf: {
|
|
13587
|
+
type: "boolean",
|
|
13588
|
+
default: false
|
|
13589
|
+
},
|
|
13590
|
+
base: {
|
|
13591
|
+
type: "string"
|
|
13592
|
+
}
|
|
13593
|
+
},
|
|
13594
|
+
strict: true,
|
|
13595
|
+
allowPositionals: true
|
|
13596
|
+
});
|
|
13597
|
+
const useFzf = values.fzf ?? false;
|
|
13598
|
+
const base = values.base;
|
|
13599
|
+
if (useFzf) {
|
|
13600
|
+
if (positionals.length > 0) {
|
|
13601
|
+
exitWithError(
|
|
13602
|
+
"Cannot specify worktree name when using --fzf",
|
|
13603
|
+
exitCodes.validationError
|
|
13604
|
+
);
|
|
13605
|
+
}
|
|
13606
|
+
} else {
|
|
13607
|
+
if (positionals.length !== 1) {
|
|
13608
|
+
exitWithError(
|
|
13609
|
+
"Usage: phantom review <worktree-name> [--base <ref>]",
|
|
13610
|
+
exitCodes.validationError
|
|
13611
|
+
);
|
|
13612
|
+
}
|
|
13613
|
+
}
|
|
13614
|
+
try {
|
|
13615
|
+
const gitRoot = await getGitRoot();
|
|
13616
|
+
const context = await createContext(gitRoot);
|
|
13617
|
+
let worktreeName;
|
|
13618
|
+
if (useFzf) {
|
|
13619
|
+
const selectResult = await selectWorktreeWithFzf(
|
|
13620
|
+
context.gitRoot,
|
|
13621
|
+
context.worktreesDirectory
|
|
13622
|
+
);
|
|
13623
|
+
if (isErr(selectResult)) {
|
|
13624
|
+
exitWithError(selectResult.error.message, exitCodes.generalError);
|
|
13625
|
+
}
|
|
13626
|
+
if (!selectResult.value) {
|
|
13627
|
+
exitWithSuccess();
|
|
13628
|
+
}
|
|
13629
|
+
worktreeName = selectResult.value.name;
|
|
13630
|
+
} else {
|
|
13631
|
+
worktreeName = positionals[0];
|
|
13632
|
+
}
|
|
13633
|
+
const validation = await validateWorktreeExists(
|
|
13634
|
+
context.gitRoot,
|
|
13635
|
+
context.worktreesDirectory,
|
|
13636
|
+
worktreeName
|
|
13637
|
+
);
|
|
13638
|
+
if (isErr(validation)) {
|
|
13639
|
+
exitWithError(validation.error.message, exitCodes.generalError);
|
|
13640
|
+
}
|
|
13641
|
+
const baseRef = base ?? `origin/${context.config?.defaultBranch ?? "main"}`;
|
|
13642
|
+
output.log(`Opening review for worktree '${worktreeName}'...`);
|
|
13643
|
+
output.log(
|
|
13644
|
+
"powered by yoshiko-pg/reviewit (https://github.com/yoshiko-pg/reviewit)"
|
|
13645
|
+
);
|
|
13646
|
+
const command2 = ["reviewit", ".", baseRef];
|
|
13647
|
+
const result = await execInWorktree(
|
|
13648
|
+
context.gitRoot,
|
|
13649
|
+
context.worktreesDirectory,
|
|
13650
|
+
worktreeName,
|
|
13651
|
+
command2,
|
|
13652
|
+
{ interactive: true }
|
|
13653
|
+
);
|
|
13654
|
+
if (isErr(result)) {
|
|
13655
|
+
const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.notFound : result.error.exitCode || exitCodes.generalError;
|
|
13656
|
+
exitWithError(result.error.message, exitCode);
|
|
13657
|
+
}
|
|
13658
|
+
process.exit(result.value.exitCode);
|
|
13659
|
+
} catch (error) {
|
|
13660
|
+
exitWithError(
|
|
13661
|
+
error instanceof Error ? error.message : String(error),
|
|
13662
|
+
exitCodes.generalError
|
|
13663
|
+
);
|
|
13664
|
+
}
|
|
13665
|
+
}
|
|
13666
|
+
|
|
13667
|
+
// src/handlers/shell.ts
|
|
13668
|
+
import { parseArgs as parseArgs9 } from "node:util";
|
|
13669
|
+
async function shellHandler(args2) {
|
|
13670
|
+
const { positionals, values } = parseArgs9({
|
|
13508
13671
|
args: args2,
|
|
13509
13672
|
options: {
|
|
13510
13673
|
fzf: {
|
|
@@ -13628,12 +13791,12 @@ async function shellHandler(args2) {
|
|
|
13628
13791
|
}
|
|
13629
13792
|
|
|
13630
13793
|
// src/handlers/version.ts
|
|
13631
|
-
import { parseArgs as
|
|
13794
|
+
import { parseArgs as parseArgs10 } from "node:util";
|
|
13632
13795
|
|
|
13633
13796
|
// package.json
|
|
13634
13797
|
var package_default2 = {
|
|
13635
13798
|
name: "@aku11i/phantom-cli",
|
|
13636
|
-
version: "1.
|
|
13799
|
+
version: "2.1.0",
|
|
13637
13800
|
private: true,
|
|
13638
13801
|
type: "module",
|
|
13639
13802
|
scripts: {
|
|
@@ -13661,7 +13824,7 @@ function getVersion() {
|
|
|
13661
13824
|
|
|
13662
13825
|
// src/handlers/version.ts
|
|
13663
13826
|
function versionHandler(args2 = []) {
|
|
13664
|
-
|
|
13827
|
+
parseArgs10({
|
|
13665
13828
|
args: args2,
|
|
13666
13829
|
options: {},
|
|
13667
13830
|
strict: true,
|
|
@@ -13673,9 +13836,9 @@ function versionHandler(args2 = []) {
|
|
|
13673
13836
|
}
|
|
13674
13837
|
|
|
13675
13838
|
// src/handlers/where.ts
|
|
13676
|
-
import { parseArgs as
|
|
13839
|
+
import { parseArgs as parseArgs11 } from "node:util";
|
|
13677
13840
|
async function whereHandler(args2) {
|
|
13678
|
-
const { positionals, values } =
|
|
13841
|
+
const { positionals, values } = parseArgs11({
|
|
13679
13842
|
args: args2,
|
|
13680
13843
|
options: {
|
|
13681
13844
|
fzf: {
|
|
@@ -14044,6 +14207,55 @@ var listHelp = {
|
|
|
14044
14207
|
]
|
|
14045
14208
|
};
|
|
14046
14209
|
|
|
14210
|
+
// src/help/review.ts
|
|
14211
|
+
var reviewHelp = {
|
|
14212
|
+
name: "review",
|
|
14213
|
+
description: "Review changes in a worktree with a local PR review interface (experimental)",
|
|
14214
|
+
usage: "phantom review [options] <worktree-name>",
|
|
14215
|
+
options: [
|
|
14216
|
+
{
|
|
14217
|
+
name: "--fzf",
|
|
14218
|
+
type: "boolean",
|
|
14219
|
+
description: "Use fzf for interactive worktree selection"
|
|
14220
|
+
},
|
|
14221
|
+
{
|
|
14222
|
+
name: "--base",
|
|
14223
|
+
type: "string",
|
|
14224
|
+
description: "Base reference for comparison (default: origin/<defaultBranch>)"
|
|
14225
|
+
}
|
|
14226
|
+
],
|
|
14227
|
+
examples: [
|
|
14228
|
+
{
|
|
14229
|
+
description: "Review changes against default branch",
|
|
14230
|
+
command: "phantom review feature-auth"
|
|
14231
|
+
},
|
|
14232
|
+
{
|
|
14233
|
+
description: "Review changes against specific remote branch",
|
|
14234
|
+
command: "phantom review feature-login --base origin/feature-auth"
|
|
14235
|
+
},
|
|
14236
|
+
{
|
|
14237
|
+
description: "Review changes against local branch",
|
|
14238
|
+
command: "phantom review feature-auth --base main"
|
|
14239
|
+
},
|
|
14240
|
+
{
|
|
14241
|
+
description: "Interactive worktree selection",
|
|
14242
|
+
command: "phantom review --fzf"
|
|
14243
|
+
},
|
|
14244
|
+
{
|
|
14245
|
+
description: "Interactive selection with custom base",
|
|
14246
|
+
command: "phantom review --fzf --base origin/staging"
|
|
14247
|
+
}
|
|
14248
|
+
],
|
|
14249
|
+
notes: [
|
|
14250
|
+
"\u26A0\uFE0F This is an experimental feature and may change in future versions",
|
|
14251
|
+
"Uses reviewit to provide a GitHub-like PR review interface locally",
|
|
14252
|
+
"Default base is origin/<defaultBranch> where defaultBranch is from config or 'main'",
|
|
14253
|
+
"The --base value is passed directly to reviewit as the comparison reference",
|
|
14254
|
+
"Requires reviewit to be installed separately (e.g., npm install -g reviewit)",
|
|
14255
|
+
"powered by yoshiko-pg/reviewit (https://github.com/yoshiko-pg/reviewit)"
|
|
14256
|
+
]
|
|
14257
|
+
};
|
|
14258
|
+
|
|
14047
14259
|
// src/help/shell.ts
|
|
14048
14260
|
var shellHelp = {
|
|
14049
14261
|
name: "shell",
|
|
@@ -14191,6 +14403,12 @@ var commands = [
|
|
|
14191
14403
|
handler: execHandler,
|
|
14192
14404
|
help: execHelp
|
|
14193
14405
|
},
|
|
14406
|
+
{
|
|
14407
|
+
name: "review",
|
|
14408
|
+
description: "Review changes in a worktree with a local PR review interface (experimental)",
|
|
14409
|
+
handler: reviewHandler,
|
|
14410
|
+
help: reviewHelp
|
|
14411
|
+
},
|
|
14194
14412
|
{
|
|
14195
14413
|
name: "shell",
|
|
14196
14414
|
description: "Open an interactive shell in a worktree directory",
|