opencode-worktree 0.1.3 → 0.2.2

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/src/ui.ts ADDED
@@ -0,0 +1,656 @@
1
+ import {
2
+ BoxRenderable,
3
+ createCliRenderer,
4
+ InputRenderable,
5
+ InputRenderableEvents,
6
+ SelectRenderable,
7
+ SelectRenderableEvents,
8
+ TextRenderable,
9
+ type CliRenderer,
10
+ type KeyEvent,
11
+ type SelectOption,
12
+ } from "@opentui/core";
13
+ import { basename } from "node:path";
14
+ import {
15
+ createWorktree,
16
+ deleteWorktree,
17
+ getDefaultWorktreesDir,
18
+ hasUncommittedChanges,
19
+ isMainWorktree,
20
+ listWorktrees,
21
+ resolveRepoRoot,
22
+ unlinkWorktree,
23
+ } from "./git.js";
24
+ import { isOpenCodeAvailable, launchOpenCode } from "./opencode.js";
25
+ import { WorktreeInfo } from "./types.js";
26
+
27
+ type StatusLevel = "info" | "warning" | "error" | "success";
28
+
29
+ const statusColors: Record<StatusLevel, string> = {
30
+ info: "#94A3B8",
31
+ warning: "#F59E0B",
32
+ error: "#EF4444",
33
+ success: "#10B981",
34
+ };
35
+
36
+ const CREATE_NEW_WORKTREE_VALUE = Symbol("CREATE_NEW_WORKTREE");
37
+
38
+ type SelectionValue = WorktreeInfo | typeof CREATE_NEW_WORKTREE_VALUE;
39
+
40
+ type ConfirmAction = "unlink" | "delete" | "cancel";
41
+
42
+ const CONFIRM_UNLINK_VALUE: ConfirmAction = "unlink";
43
+ const CONFIRM_DELETE_VALUE: ConfirmAction = "delete";
44
+ const CONFIRM_CANCEL_VALUE: ConfirmAction = "cancel";
45
+
46
+ export const runApp = async (targetPath: string): Promise<void> => {
47
+ const renderer = await createCliRenderer({
48
+ exitOnCtrlC: false,
49
+ targetFps: 30,
50
+ });
51
+
52
+ renderer.setBackgroundColor("transparent");
53
+ new WorktreeSelector(renderer, targetPath);
54
+ };
55
+
56
+ class WorktreeSelector {
57
+ private selectElement: SelectRenderable;
58
+ private statusText: TextRenderable;
59
+ private instructions: TextRenderable;
60
+ private title: TextRenderable;
61
+
62
+ private inputContainer: BoxRenderable | null = null;
63
+ private branchInput: InputRenderable | null = null;
64
+
65
+ private confirmContainer: BoxRenderable | null = null;
66
+ private confirmSelect: SelectRenderable | null = null;
67
+ private confirmingWorktree: WorktreeInfo | null = null;
68
+ private isConfirming = false;
69
+
70
+ private opencodeAvailable = false;
71
+ private repoRoot: string | null = null;
72
+ private isCreatingWorktree = false;
73
+ private worktreeOptions: SelectOption[] = [];
74
+
75
+ constructor(
76
+ private renderer: CliRenderer,
77
+ private targetPath: string,
78
+ ) {
79
+ // Load worktrees first to get initial options
80
+ this.repoRoot = resolveRepoRoot(this.targetPath);
81
+ this.opencodeAvailable = isOpenCodeAvailable();
82
+ this.worktreeOptions = this.buildInitialOptions();
83
+
84
+ this.title = new TextRenderable(renderer, {
85
+ id: "worktree-title",
86
+ position: "absolute",
87
+ left: 2,
88
+ top: 1,
89
+ content: "OPENCODE WORKTREES",
90
+ fg: "#E2E8F0",
91
+ });
92
+ this.renderer.root.add(this.title);
93
+
94
+ this.selectElement = new SelectRenderable(renderer, {
95
+ id: "worktree-selector",
96
+ position: "absolute",
97
+ left: 2,
98
+ top: 3,
99
+ width: 76,
100
+ height: 15,
101
+ options: this.worktreeOptions,
102
+ backgroundColor: "#0F172A",
103
+ focusedBackgroundColor: "#1E293B",
104
+ selectedBackgroundColor: "#1E3A5F",
105
+ textColor: "#E2E8F0",
106
+ selectedTextColor: "#38BDF8",
107
+ descriptionColor: "#94A3B8",
108
+ selectedDescriptionColor: "#E2E8F0",
109
+ showScrollIndicator: true,
110
+ wrapSelection: true,
111
+ showDescription: true,
112
+ fastScrollStep: 5,
113
+ });
114
+ this.renderer.root.add(this.selectElement);
115
+
116
+ this.statusText = new TextRenderable(renderer, {
117
+ id: "worktree-status",
118
+ position: "absolute",
119
+ left: 2,
120
+ top: 19,
121
+ content: this.getInitialStatusMessage(),
122
+ fg: this.getInitialStatusColor(),
123
+ });
124
+ this.renderer.root.add(this.statusText);
125
+
126
+ this.instructions = new TextRenderable(renderer, {
127
+ id: "worktree-instructions",
128
+ position: "absolute",
129
+ left: 2,
130
+ top: 20,
131
+ content:
132
+ "↑/↓ navigate • Enter open • d delete • n new • r refresh • q quit",
133
+ fg: "#64748B",
134
+ });
135
+ this.renderer.root.add(this.instructions);
136
+
137
+ this.selectElement.on(
138
+ SelectRenderableEvents.ITEM_SELECTED,
139
+ (_index: number, option: SelectOption) => {
140
+ // Ignore if we're in another mode
141
+ if (this.isConfirming || this.isCreatingWorktree) {
142
+ return;
143
+ }
144
+ this.handleSelection(option.value as SelectionValue);
145
+ },
146
+ );
147
+
148
+ this.renderer.keyInput.on("keypress", (key: KeyEvent) => {
149
+ this.handleKeypress(key);
150
+ });
151
+
152
+ this.selectElement.focus();
153
+ }
154
+
155
+ private getInitialStatusMessage(): string {
156
+ if (!this.repoRoot) {
157
+ return "No git repository found in this directory.";
158
+ }
159
+ if (!this.opencodeAvailable) {
160
+ return "opencode is not available on PATH.";
161
+ }
162
+ const count = this.worktreeOptions.length - 1; // subtract create option
163
+ if (count === 0) {
164
+ return "No worktrees detected. Select 'Create new worktree' to add one.";
165
+ }
166
+ return `Found ${count} worktree${count === 1 ? "" : "s"}.`;
167
+ }
168
+
169
+ private getInitialStatusColor(): string {
170
+ if (!this.repoRoot || !this.opencodeAvailable) {
171
+ return statusColors.error;
172
+ }
173
+ return statusColors.info;
174
+ }
175
+
176
+ private buildInitialOptions(): SelectOption[] {
177
+ if (!this.repoRoot) {
178
+ return [];
179
+ }
180
+
181
+ const worktrees = listWorktrees(this.repoRoot);
182
+ return this.buildOptions(worktrees);
183
+ }
184
+
185
+ private handleKeypress(key: KeyEvent): void {
186
+ if (key.ctrl && key.name === "c") {
187
+ this.cleanup(true);
188
+ return;
189
+ }
190
+
191
+ // Handle confirmation mode
192
+ if (this.isConfirming) {
193
+ if (key.name === "escape") {
194
+ this.hideConfirmDialog();
195
+ }
196
+ return;
197
+ }
198
+
199
+ if (this.isCreatingWorktree) {
200
+ if (key.name === "escape") {
201
+ this.hideCreateWorktreeInput();
202
+ }
203
+ return;
204
+ }
205
+
206
+ if (key.name === "q" || key.name === "escape") {
207
+ this.cleanup(true);
208
+ return;
209
+ }
210
+
211
+ if (key.name === "r") {
212
+ this.loadWorktrees();
213
+ return;
214
+ }
215
+
216
+ // 'n' for new worktree
217
+ if (key.name === "n") {
218
+ this.showCreateWorktreeInput();
219
+ return;
220
+ }
221
+
222
+ // 'd' for delete/unlink menu
223
+ if (key.name === "d") {
224
+ this.showDeleteConfirmation();
225
+ return;
226
+ }
227
+ }
228
+
229
+ private handleSelection(value: SelectionValue): void {
230
+ if (value === CREATE_NEW_WORKTREE_VALUE) {
231
+ this.showCreateWorktreeInput();
232
+ return;
233
+ }
234
+
235
+ const worktree = value as WorktreeInfo;
236
+ if (!this.opencodeAvailable) {
237
+ this.setStatus("opencode is not available on PATH.", "error");
238
+ return;
239
+ }
240
+
241
+ this.cleanup(false);
242
+ launchOpenCode(worktree.path);
243
+ }
244
+
245
+ private showCreateWorktreeInput(): void {
246
+ this.isCreatingWorktree = true;
247
+ this.selectElement.visible = false;
248
+ this.selectElement.blur();
249
+
250
+ this.inputContainer = new BoxRenderable(this.renderer, {
251
+ id: "worktree-input-container",
252
+ position: "absolute",
253
+ left: 2,
254
+ top: 3,
255
+ width: 76,
256
+ height: 5,
257
+ borderStyle: "single",
258
+ borderColor: "#38BDF8",
259
+ title: "Create New Worktree",
260
+ titleAlignment: "center",
261
+ backgroundColor: "#0F172A",
262
+ border: true,
263
+ });
264
+ this.renderer.root.add(this.inputContainer);
265
+
266
+ const inputLabel = new TextRenderable(this.renderer, {
267
+ id: "worktree-input-label",
268
+ position: "absolute",
269
+ left: 1,
270
+ top: 1,
271
+ content: "Branch name:",
272
+ fg: "#E2E8F0",
273
+ });
274
+ this.inputContainer.add(inputLabel);
275
+
276
+ this.branchInput = new InputRenderable(this.renderer, {
277
+ id: "worktree-branch-input",
278
+ position: "absolute",
279
+ left: 14,
280
+ top: 1,
281
+ width: 58,
282
+ placeholder: "feature/my-new-branch",
283
+ focusedBackgroundColor: "#1E293B",
284
+ backgroundColor: "#1E293B",
285
+ });
286
+ this.inputContainer.add(this.branchInput);
287
+
288
+ this.branchInput.on(InputRenderableEvents.CHANGE, (value: string) => {
289
+ this.handleCreateWorktree(value);
290
+ });
291
+
292
+ this.instructions.content = "Enter to create - Esc to cancel";
293
+ this.setStatus("Enter a branch name for the new worktree.", "info");
294
+
295
+ this.branchInput.focus();
296
+ this.renderer.requestRender();
297
+ }
298
+
299
+ private hideCreateWorktreeInput(): void {
300
+ this.isCreatingWorktree = false;
301
+
302
+ if (this.branchInput) {
303
+ this.branchInput.blur();
304
+ }
305
+
306
+ if (this.inputContainer) {
307
+ this.renderer.root.remove(this.inputContainer.id);
308
+ this.inputContainer = null;
309
+ this.branchInput = null;
310
+ }
311
+
312
+ this.selectElement.visible = true;
313
+ this.instructions.content =
314
+ "↑/↓ navigate • Enter open • d delete • n new • r refresh • q quit";
315
+ this.selectElement.focus();
316
+ this.loadWorktrees();
317
+ }
318
+
319
+ private handleCreateWorktree(branchName: string): void {
320
+ const trimmed = branchName.trim();
321
+ if (!trimmed) {
322
+ this.setStatus("Branch name cannot be empty.", "error");
323
+ return;
324
+ }
325
+
326
+ if (!this.repoRoot) {
327
+ this.setStatus("No git repository found.", "error");
328
+ return;
329
+ }
330
+
331
+ const worktreesDir = getDefaultWorktreesDir(this.repoRoot);
332
+ this.setStatus(`Creating worktree for branch '${trimmed}'...`, "info");
333
+ this.renderer.requestRender();
334
+
335
+ const result = createWorktree(this.repoRoot, trimmed, worktreesDir);
336
+
337
+ if (result.success) {
338
+ this.setStatus(`Worktree created at ${result.path}`, "success");
339
+ this.renderer.requestRender();
340
+
341
+ if (this.opencodeAvailable) {
342
+ this.hideCreateWorktreeInput();
343
+ this.cleanup(false);
344
+ launchOpenCode(result.path);
345
+ } else {
346
+ this.setStatus(
347
+ `Worktree created but opencode is not available.`,
348
+ "warning",
349
+ );
350
+ this.hideCreateWorktreeInput();
351
+ }
352
+ } else {
353
+ this.setStatus(`Failed to create worktree: ${result.error}`, "error");
354
+ }
355
+ }
356
+
357
+ private loadWorktrees(): void {
358
+ this.repoRoot = resolveRepoRoot(this.targetPath);
359
+ if (!this.repoRoot) {
360
+ this.setStatus("No git repository found in this directory.", "error");
361
+ this.selectElement.options = [];
362
+ this.renderer.requestRender();
363
+ return;
364
+ }
365
+
366
+ const worktrees = listWorktrees(this.repoRoot);
367
+ this.selectElement.options = this.buildOptions(worktrees);
368
+
369
+ if (worktrees.length === 0) {
370
+ this.setStatus(
371
+ "No worktrees detected. Select 'Create new worktree' to add one.",
372
+ "info",
373
+ );
374
+ } else {
375
+ this.setStatus(
376
+ `Found ${worktrees.length} worktree${worktrees.length === 1 ? "" : "s"}.`,
377
+ "info",
378
+ );
379
+ }
380
+
381
+ this.opencodeAvailable = isOpenCodeAvailable();
382
+ if (!this.opencodeAvailable) {
383
+ this.setStatus("opencode is not available on PATH.", "error");
384
+ }
385
+
386
+ this.renderer.requestRender();
387
+ }
388
+
389
+ private buildOptions(worktrees: WorktreeInfo[]): SelectOption[] {
390
+ const createOption: SelectOption = {
391
+ name: "+ Create new worktree",
392
+ description: "Create a new worktree from a branch",
393
+ value: CREATE_NEW_WORKTREE_VALUE,
394
+ };
395
+
396
+ const worktreeOptions = worktrees.map((worktree) => {
397
+ const shortHead = worktree.head ? worktree.head.slice(0, 7) : "unknown";
398
+ const baseName = basename(worktree.path);
399
+ const label = worktree.branch
400
+ ? worktree.branch
401
+ : worktree.isDetached
402
+ ? `${baseName} (detached)`
403
+ : baseName;
404
+
405
+ return {
406
+ name: label,
407
+ description: `${worktree.path} - ${shortHead}`,
408
+ value: worktree,
409
+ };
410
+ });
411
+
412
+ return [createOption, ...worktreeOptions];
413
+ }
414
+
415
+ private setStatus(message: string, level: StatusLevel): void {
416
+ this.statusText.content = message;
417
+ this.statusText.fg = statusColors[level];
418
+ }
419
+
420
+ private getSelectedWorktree(): WorktreeInfo | null {
421
+ const selectedIndex = this.selectElement.getSelectedIndex();
422
+ const option = this.selectElement.options[selectedIndex];
423
+ if (!option || option.value === CREATE_NEW_WORKTREE_VALUE) {
424
+ return null;
425
+ }
426
+ return option.value as WorktreeInfo;
427
+ }
428
+
429
+ private showDeleteConfirmation(): void {
430
+ const worktree = this.getSelectedWorktree();
431
+ if (!worktree) {
432
+ this.setStatus("Select a worktree to delete.", "warning");
433
+ return;
434
+ }
435
+
436
+ if (!this.repoRoot) {
437
+ this.setStatus("No git repository found.", "error");
438
+ return;
439
+ }
440
+
441
+ // Check if this is the main worktree
442
+ if (isMainWorktree(this.repoRoot, worktree.path)) {
443
+ this.setStatus("Cannot delete the main worktree.", "error");
444
+ return;
445
+ }
446
+
447
+ this.isConfirming = true;
448
+ this.confirmingWorktree = worktree;
449
+ this.selectElement.visible = false;
450
+ this.selectElement.blur();
451
+
452
+ // Check for uncommitted changes
453
+ const isDirty = hasUncommittedChanges(worktree.path);
454
+ const branchDisplay = worktree.branch || basename(worktree.path);
455
+
456
+ // Build dialog title
457
+ const title = `Remove: ${branchDisplay}`;
458
+
459
+ this.confirmContainer = new BoxRenderable(this.renderer, {
460
+ id: "confirm-container",
461
+ position: "absolute",
462
+ left: 2,
463
+ top: 3,
464
+ width: 76,
465
+ height: isDirty ? 10 : 8,
466
+ borderStyle: "single",
467
+ borderColor: "#F59E0B",
468
+ title,
469
+ titleAlignment: "center",
470
+ backgroundColor: "#0F172A",
471
+ border: true,
472
+ });
473
+ this.renderer.root.add(this.confirmContainer);
474
+
475
+ // Warning for dirty worktree
476
+ let yOffset = 1;
477
+ if (isDirty) {
478
+ const warningText = new TextRenderable(this.renderer, {
479
+ id: "confirm-warning",
480
+ position: "absolute",
481
+ left: 1,
482
+ top: yOffset,
483
+ content: "⚠ This worktree has uncommitted changes!",
484
+ fg: "#F59E0B",
485
+ });
486
+ this.confirmContainer.add(warningText);
487
+ yOffset += 2;
488
+ }
489
+
490
+ const pathText = new TextRenderable(this.renderer, {
491
+ id: "confirm-path",
492
+ position: "absolute",
493
+ left: 1,
494
+ top: yOffset,
495
+ content: `Path: ${worktree.path}`,
496
+ fg: "#94A3B8",
497
+ });
498
+ this.confirmContainer.add(pathText);
499
+ yOffset += 2;
500
+
501
+ // Build options - Unlink is default (first)
502
+ const options: SelectOption[] = [
503
+ {
504
+ name: "Unlink (default)",
505
+ description: "Remove worktree directory, keep branch for later use",
506
+ value: CONFIRM_UNLINK_VALUE,
507
+ },
508
+ {
509
+ name: "Delete",
510
+ description: "Remove worktree AND delete local branch (never remote)",
511
+ value: CONFIRM_DELETE_VALUE,
512
+ },
513
+ {
514
+ name: "Cancel",
515
+ description: "Go back without changes",
516
+ value: CONFIRM_CANCEL_VALUE,
517
+ },
518
+ ];
519
+
520
+ this.confirmSelect = new SelectRenderable(this.renderer, {
521
+ id: "confirm-select",
522
+ position: "absolute",
523
+ left: 1,
524
+ top: yOffset,
525
+ width: 72,
526
+ height: 4,
527
+ options,
528
+ backgroundColor: "#0F172A",
529
+ focusedBackgroundColor: "#1E293B",
530
+ selectedBackgroundColor: "#1E3A5F",
531
+ textColor: "#E2E8F0",
532
+ selectedTextColor: "#38BDF8",
533
+ descriptionColor: "#94A3B8",
534
+ selectedDescriptionColor: "#E2E8F0",
535
+ showDescription: true,
536
+ wrapSelection: true,
537
+ });
538
+ this.confirmContainer.add(this.confirmSelect);
539
+
540
+ this.confirmSelect.on(
541
+ SelectRenderableEvents.ITEM_SELECTED,
542
+ (_index: number, option: SelectOption) => {
543
+ this.handleConfirmAction(option.value as ConfirmAction, isDirty);
544
+ },
545
+ );
546
+
547
+ this.instructions.content =
548
+ "↑/↓ select action • Enter confirm • Esc cancel";
549
+ this.setStatus(
550
+ isDirty
551
+ ? "Warning: Uncommitted changes will be lost!"
552
+ : "Choose how to remove this worktree.",
553
+ isDirty ? "warning" : "info",
554
+ );
555
+
556
+ this.confirmSelect.focus();
557
+ this.renderer.requestRender();
558
+ }
559
+
560
+ private hideConfirmDialog(): void {
561
+ this.isConfirming = false;
562
+ this.confirmingWorktree = null;
563
+
564
+ if (this.confirmSelect) {
565
+ this.confirmSelect.blur();
566
+ }
567
+
568
+ if (this.confirmContainer) {
569
+ this.renderer.root.remove(this.confirmContainer.id);
570
+ this.confirmContainer = null;
571
+ this.confirmSelect = null;
572
+ }
573
+
574
+ this.selectElement.visible = true;
575
+ this.instructions.content =
576
+ "↑/↓ navigate • Enter open • d delete • n new • r refresh • q quit";
577
+ this.selectElement.focus();
578
+ this.loadWorktrees();
579
+ }
580
+
581
+ private handleConfirmAction(action: ConfirmAction, isDirty: boolean): void {
582
+ if (action === CONFIRM_CANCEL_VALUE) {
583
+ this.hideConfirmDialog();
584
+ return;
585
+ }
586
+
587
+ if (!this.confirmingWorktree || !this.repoRoot) {
588
+ this.hideConfirmDialog();
589
+ return;
590
+ }
591
+
592
+ const worktree = this.confirmingWorktree;
593
+ const branchName = worktree.branch || basename(worktree.path);
594
+
595
+ if (action === CONFIRM_UNLINK_VALUE) {
596
+ // Unlink: remove worktree, keep branch
597
+ this.setStatus(`Unlinking worktree '${branchName}'...`, "info");
598
+ this.renderer.requestRender();
599
+
600
+ const result = unlinkWorktree(this.repoRoot, worktree.path, isDirty);
601
+ if (result.success) {
602
+ this.setStatus(
603
+ `Worktree unlinked. Branch '${branchName}' is still available.`,
604
+ "success",
605
+ );
606
+ } else {
607
+ this.setStatus(`Failed to unlink: ${result.error}`, "error");
608
+ }
609
+ } else if (action === CONFIRM_DELETE_VALUE) {
610
+ // Delete: remove worktree AND local branch
611
+ if (!worktree.branch) {
612
+ this.setStatus("Cannot delete branch: detached HEAD.", "error");
613
+ this.hideConfirmDialog();
614
+ return;
615
+ }
616
+
617
+ this.setStatus(`Deleting worktree and branch '${branchName}'...`, "info");
618
+ this.renderer.requestRender();
619
+
620
+ const result = deleteWorktree(
621
+ this.repoRoot,
622
+ worktree.path,
623
+ worktree.branch,
624
+ isDirty,
625
+ );
626
+ if (result.success) {
627
+ this.setStatus(
628
+ `Worktree and local branch '${branchName}' deleted.`,
629
+ "success",
630
+ );
631
+ } else {
632
+ const stepMsg =
633
+ result.step === "unlink"
634
+ ? "Failed to remove worktree"
635
+ : "Worktree removed but failed to delete branch";
636
+ this.setStatus(`${stepMsg}: ${result.error}`, "error");
637
+ }
638
+ }
639
+
640
+ this.hideConfirmDialog();
641
+ }
642
+
643
+ private cleanup(shouldExit: boolean): void {
644
+ this.selectElement.blur();
645
+ if (this.branchInput) {
646
+ this.branchInput.blur();
647
+ }
648
+ if (this.confirmSelect) {
649
+ this.confirmSelect.blur();
650
+ }
651
+ this.renderer.destroy();
652
+ if (shouldExit) {
653
+ process.exit(0);
654
+ }
655
+ }
656
+ }
package/postinstall.mjs DELETED
@@ -1,69 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import os from "node:os";
6
- import { fileURLToPath } from "node:url";
7
- import { createRequire } from "node:module";
8
-
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const require = createRequire(import.meta.url);
11
-
12
- const detectPlatformAndArch = () => {
13
- let platform;
14
- switch (os.platform()) {
15
- case "darwin":
16
- platform = "darwin";
17
- break;
18
- case "linux":
19
- platform = "linux";
20
- break;
21
- case "win32":
22
- platform = "windows";
23
- break;
24
- default:
25
- platform = os.platform();
26
- break;
27
- }
28
-
29
- let arch;
30
- switch (os.arch()) {
31
- case "x64":
32
- arch = "x64";
33
- break;
34
- case "arm64":
35
- arch = "arm64";
36
- break;
37
- default:
38
- arch = os.arch();
39
- break;
40
- }
41
-
42
- return { platform, arch };
43
- };
44
-
45
- const verifyBinary = () => {
46
- const { platform, arch } = detectPlatformAndArch();
47
- const packageName = `opencode-worktree-${platform}-${arch}`;
48
- const binaryName =
49
- platform === "windows" ? "opencode-worktree.exe" : "opencode-worktree";
50
-
51
- const packageJsonPath = require.resolve(`${packageName}/package.json`);
52
- const packageDir = path.dirname(packageJsonPath);
53
- const binaryPath = path.join(packageDir, "bin", binaryName);
54
-
55
- if (!fs.existsSync(binaryPath)) {
56
- throw new Error(`Binary not found at ${binaryPath}`);
57
- }
58
-
59
- return binaryPath;
60
- };
61
-
62
- try {
63
- const binaryPath = verifyBinary();
64
- console.log(`opencode-worktree binary verified at: ${binaryPath}`);
65
- } catch (error) {
66
- console.error("Failed to setup opencode-worktree binary.");
67
- console.error(error instanceof Error ? error.message : String(error));
68
- process.exit(1);
69
- }