opencode-worktree 0.3.1 → 0.3.3
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/bin/opencode-worktree +0 -3
- package/package.json +2 -1
- package/script/build.ts +2 -0
- package/src/cli.ts +11 -1
- package/src/ui.ts +103 -40
package/bin/opencode-worktree
CHANGED
|
@@ -5,7 +5,6 @@ import path from 'node:path';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import updateNotifier from 'update-notifier';
|
|
9
8
|
|
|
10
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
10
|
const __dirname = path.dirname(__filename);
|
|
@@ -13,8 +12,6 @@ const pkg = JSON.parse(
|
|
|
13
12
|
fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
14
13
|
);
|
|
15
14
|
|
|
16
|
-
updateNotifier({ pkg }).notify({ isGlobal: true });
|
|
17
|
-
|
|
18
15
|
const detectPlatformAndArch = () => {
|
|
19
16
|
let platform;
|
|
20
17
|
switch (os.platform()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-worktree",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "TUI for managing git worktrees with opencode integration.",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/bun": "^1.2.0",
|
|
50
50
|
"@types/node": "^24.2.0",
|
|
51
|
+
"@types/update-notifier": "^6.0.8",
|
|
51
52
|
"typescript": "^5.9.3"
|
|
52
53
|
}
|
|
53
54
|
}
|
package/script/build.ts
CHANGED
|
@@ -107,6 +107,8 @@ const buildBinary = async (target: Target) => {
|
|
|
107
107
|
OTUI_TREE_SITTER_WORKER_PATH: JSON.stringify(
|
|
108
108
|
bunfsRoot + workerRelativePath,
|
|
109
109
|
),
|
|
110
|
+
__PACKAGE_VERSION__: JSON.stringify(version),
|
|
111
|
+
__PACKAGE_NAME__: JSON.stringify(packageName),
|
|
110
112
|
},
|
|
111
113
|
});
|
|
112
114
|
} catch (error) {
|
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { runApp } from "./ui.js";
|
|
2
2
|
|
|
3
|
+
// Build-time injected constants (defined in script/build.ts)
|
|
4
|
+
// Use typeof check to provide fallbacks for dev mode (bun run dev)
|
|
5
|
+
declare const __PACKAGE_VERSION__: string | undefined;
|
|
6
|
+
declare const __PACKAGE_NAME__: string | undefined;
|
|
7
|
+
|
|
8
|
+
const pkg = {
|
|
9
|
+
name: typeof __PACKAGE_NAME__ !== "undefined" ? __PACKAGE_NAME__ : "opencode-worktree",
|
|
10
|
+
version: typeof __PACKAGE_VERSION__ !== "undefined" ? __PACKAGE_VERSION__ : "dev",
|
|
11
|
+
};
|
|
12
|
+
|
|
3
13
|
// Accept optional path argument: opencode-worktree [path]
|
|
4
14
|
const targetPath = process.argv[2] || process.cwd();
|
|
5
15
|
|
|
6
|
-
runApp(targetPath).catch((error: unknown) => {
|
|
16
|
+
runApp(targetPath, pkg).catch((error: unknown) => {
|
|
7
17
|
console.error("Failed to start OpenTUI worktree selector.");
|
|
8
18
|
console.error(error);
|
|
9
19
|
process.exit(1);
|
package/src/ui.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type KeyEvent,
|
|
11
11
|
type SelectOption,
|
|
12
12
|
} from "@opentui/core";
|
|
13
|
+
import updateNotifier from "update-notifier";
|
|
13
14
|
import { basename } from "node:path";
|
|
14
15
|
import {
|
|
15
16
|
createWorktree,
|
|
@@ -45,14 +46,22 @@ const CONFIRM_UNLINK_VALUE: ConfirmAction = "unlink";
|
|
|
45
46
|
const CONFIRM_DELETE_VALUE: ConfirmAction = "delete";
|
|
46
47
|
const CONFIRM_CANCEL_VALUE: ConfirmAction = "cancel";
|
|
47
48
|
|
|
48
|
-
export
|
|
49
|
+
export type PackageInfo = {
|
|
50
|
+
name: string;
|
|
51
|
+
version: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const runApp = async (
|
|
55
|
+
targetPath: string,
|
|
56
|
+
pkg?: PackageInfo,
|
|
57
|
+
): Promise<void> => {
|
|
49
58
|
const renderer = await createCliRenderer({
|
|
50
59
|
exitOnCtrlC: false,
|
|
51
60
|
targetFps: 30,
|
|
52
61
|
});
|
|
53
62
|
|
|
54
63
|
renderer.setBackgroundColor("transparent");
|
|
55
|
-
new WorktreeSelector(renderer, targetPath);
|
|
64
|
+
new WorktreeSelector(renderer, targetPath, pkg);
|
|
56
65
|
};
|
|
57
66
|
|
|
58
67
|
class WorktreeSelector {
|
|
@@ -60,6 +69,7 @@ class WorktreeSelector {
|
|
|
60
69
|
private statusText: TextRenderable;
|
|
61
70
|
private instructions: TextRenderable;
|
|
62
71
|
private title: TextRenderable;
|
|
72
|
+
private versionNotice: TextRenderable | null = null;
|
|
63
73
|
|
|
64
74
|
private inputContainer: BoxRenderable | null = null;
|
|
65
75
|
private branchInput: InputRenderable | null = null;
|
|
@@ -99,6 +109,7 @@ class WorktreeSelector {
|
|
|
99
109
|
constructor(
|
|
100
110
|
private renderer: CliRenderer,
|
|
101
111
|
private targetPath: string,
|
|
112
|
+
private pkg?: PackageInfo,
|
|
102
113
|
) {
|
|
103
114
|
// Load worktrees first to get initial options
|
|
104
115
|
this.repoRoot = resolveRepoRoot(this.targetPath);
|
|
@@ -115,6 +126,34 @@ class WorktreeSelector {
|
|
|
115
126
|
});
|
|
116
127
|
this.renderer.root.add(this.title);
|
|
117
128
|
|
|
129
|
+
// Display version or update notification in title line
|
|
130
|
+
if (this.pkg) {
|
|
131
|
+
const notifier = updateNotifier({ pkg: this.pkg });
|
|
132
|
+
|
|
133
|
+
let noticeContent: string;
|
|
134
|
+
let noticeColor: string;
|
|
135
|
+
|
|
136
|
+
if (notifier.update) {
|
|
137
|
+
// Update available
|
|
138
|
+
noticeContent = `Update: ${notifier.update.current} → ${notifier.update.latest} (npm i -g)`;
|
|
139
|
+
noticeColor = "#F59E0B"; // Amber
|
|
140
|
+
} else {
|
|
141
|
+
// On latest version
|
|
142
|
+
noticeContent = `v${this.pkg.version}`;
|
|
143
|
+
noticeColor = "#64748B"; // Subtle gray
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.versionNotice = new TextRenderable(renderer, {
|
|
147
|
+
id: "version-notice",
|
|
148
|
+
position: "absolute",
|
|
149
|
+
left: 78 - noticeContent.length,
|
|
150
|
+
top: 1,
|
|
151
|
+
content: noticeContent,
|
|
152
|
+
fg: noticeColor,
|
|
153
|
+
});
|
|
154
|
+
this.renderer.root.add(this.versionNotice);
|
|
155
|
+
}
|
|
156
|
+
|
|
118
157
|
this.selectElement = new SelectRenderable(renderer, {
|
|
119
158
|
id: "worktree-selector",
|
|
120
159
|
position: "absolute",
|
|
@@ -1024,7 +1063,7 @@ class WorktreeSelector {
|
|
|
1024
1063
|
const branchDisplay = worktree.branch || basename(worktree.path);
|
|
1025
1064
|
|
|
1026
1065
|
// Build dialog title
|
|
1027
|
-
const title =
|
|
1066
|
+
const title = "DELETE WORKTREE";
|
|
1028
1067
|
|
|
1029
1068
|
this.confirmContainer = new BoxRenderable(this.renderer, {
|
|
1030
1069
|
id: "confirm-container",
|
|
@@ -1032,18 +1071,30 @@ class WorktreeSelector {
|
|
|
1032
1071
|
left: 2,
|
|
1033
1072
|
top: 3,
|
|
1034
1073
|
width: 76,
|
|
1035
|
-
height: isDirty ?
|
|
1074
|
+
height: isDirty ? 12 : 10,
|
|
1036
1075
|
borderStyle: "single",
|
|
1037
|
-
borderColor: "#
|
|
1076
|
+
borderColor: "#EF4444",
|
|
1038
1077
|
title,
|
|
1039
1078
|
titleAlignment: "center",
|
|
1040
|
-
backgroundColor: "#
|
|
1079
|
+
backgroundColor: "#1C1917",
|
|
1041
1080
|
border: true,
|
|
1042
1081
|
});
|
|
1043
1082
|
this.renderer.root.add(this.confirmContainer);
|
|
1044
1083
|
|
|
1045
|
-
//
|
|
1084
|
+
// Branch name prominently displayed
|
|
1046
1085
|
let yOffset = 1;
|
|
1086
|
+
const branchHeader = new TextRenderable(this.renderer, {
|
|
1087
|
+
id: "confirm-branch",
|
|
1088
|
+
position: "absolute",
|
|
1089
|
+
left: 1,
|
|
1090
|
+
top: yOffset,
|
|
1091
|
+
content: `Branch: ${branchDisplay}`,
|
|
1092
|
+
fg: "#FBBF24",
|
|
1093
|
+
});
|
|
1094
|
+
this.confirmContainer.add(branchHeader);
|
|
1095
|
+
yOffset += 2;
|
|
1096
|
+
|
|
1097
|
+
// Warning for dirty worktree
|
|
1047
1098
|
if (isDirty) {
|
|
1048
1099
|
const warningText = new TextRenderable(this.renderer, {
|
|
1049
1100
|
id: "confirm-warning",
|
|
@@ -1051,7 +1102,7 @@ class WorktreeSelector {
|
|
|
1051
1102
|
left: 1,
|
|
1052
1103
|
top: yOffset,
|
|
1053
1104
|
content: "⚠ This worktree has uncommitted changes!",
|
|
1054
|
-
fg: "#
|
|
1105
|
+
fg: "#EF4444",
|
|
1055
1106
|
});
|
|
1056
1107
|
this.confirmContainer.add(warningText);
|
|
1057
1108
|
yOffset += 2;
|
|
@@ -1063,7 +1114,7 @@ class WorktreeSelector {
|
|
|
1063
1114
|
left: 1,
|
|
1064
1115
|
top: yOffset,
|
|
1065
1116
|
content: `Path: ${worktree.path}`,
|
|
1066
|
-
fg: "#
|
|
1117
|
+
fg: "#A8A29E",
|
|
1067
1118
|
});
|
|
1068
1119
|
this.confirmContainer.add(pathText);
|
|
1069
1120
|
yOffset += 2;
|
|
@@ -1095,13 +1146,13 @@ class WorktreeSelector {
|
|
|
1095
1146
|
width: 72,
|
|
1096
1147
|
height: 4,
|
|
1097
1148
|
options,
|
|
1098
|
-
backgroundColor: "#
|
|
1099
|
-
focusedBackgroundColor: "#
|
|
1100
|
-
selectedBackgroundColor: "#
|
|
1101
|
-
textColor: "#
|
|
1102
|
-
selectedTextColor: "#
|
|
1103
|
-
descriptionColor: "#
|
|
1104
|
-
selectedDescriptionColor: "#
|
|
1149
|
+
backgroundColor: "#1C1917",
|
|
1150
|
+
focusedBackgroundColor: "#292524",
|
|
1151
|
+
selectedBackgroundColor: "#44403C",
|
|
1152
|
+
textColor: "#E7E5E4",
|
|
1153
|
+
selectedTextColor: "#F87171",
|
|
1154
|
+
descriptionColor: "#A8A29E",
|
|
1155
|
+
selectedDescriptionColor: "#E7E5E4",
|
|
1105
1156
|
showDescription: true,
|
|
1106
1157
|
wrapSelection: true,
|
|
1107
1158
|
});
|
|
@@ -1318,7 +1369,7 @@ class WorktreeSelector {
|
|
|
1318
1369
|
const hasDirty = dirtyWorktrees.length > 0;
|
|
1319
1370
|
|
|
1320
1371
|
const count = worktrees.length;
|
|
1321
|
-
const title = `
|
|
1372
|
+
const title = `DELETE ${count} WORKTREE${count === 1 ? "" : "S"}`;
|
|
1322
1373
|
|
|
1323
1374
|
this.confirmContainer = new BoxRenderable(this.renderer, {
|
|
1324
1375
|
id: "confirm-container",
|
|
@@ -1326,18 +1377,36 @@ class WorktreeSelector {
|
|
|
1326
1377
|
left: 2,
|
|
1327
1378
|
top: 3,
|
|
1328
1379
|
width: 76,
|
|
1329
|
-
height: hasDirty ?
|
|
1380
|
+
height: hasDirty ? 14 : 12,
|
|
1330
1381
|
borderStyle: "single",
|
|
1331
|
-
borderColor: "#
|
|
1382
|
+
borderColor: "#EF4444",
|
|
1332
1383
|
title,
|
|
1333
1384
|
titleAlignment: "center",
|
|
1334
|
-
backgroundColor: "#
|
|
1385
|
+
backgroundColor: "#1C1917",
|
|
1335
1386
|
border: true,
|
|
1336
1387
|
});
|
|
1337
1388
|
this.renderer.root.add(this.confirmContainer);
|
|
1338
1389
|
|
|
1339
1390
|
let yOffset = 1;
|
|
1340
1391
|
|
|
1392
|
+
// List branches to be deleted prominently
|
|
1393
|
+
const branchNames = worktrees
|
|
1394
|
+
.map((wt) => wt.branch || basename(wt.path))
|
|
1395
|
+
.slice(0, 3);
|
|
1396
|
+
const displayList =
|
|
1397
|
+
branchNames.join(", ") + (worktrees.length > 3 ? `, +${worktrees.length - 3} more` : "");
|
|
1398
|
+
|
|
1399
|
+
const branchHeader = new TextRenderable(this.renderer, {
|
|
1400
|
+
id: "confirm-branches",
|
|
1401
|
+
position: "absolute",
|
|
1402
|
+
left: 1,
|
|
1403
|
+
top: yOffset,
|
|
1404
|
+
content: `Branches: ${displayList}`,
|
|
1405
|
+
fg: "#FBBF24",
|
|
1406
|
+
});
|
|
1407
|
+
this.confirmContainer.add(branchHeader);
|
|
1408
|
+
yOffset += 2;
|
|
1409
|
+
|
|
1341
1410
|
// Warning for dirty worktrees
|
|
1342
1411
|
if (hasDirty) {
|
|
1343
1412
|
const warningText = new TextRenderable(this.renderer, {
|
|
@@ -1346,28 +1415,22 @@ class WorktreeSelector {
|
|
|
1346
1415
|
left: 1,
|
|
1347
1416
|
top: yOffset,
|
|
1348
1417
|
content: `⚠ ${dirtyWorktrees.length} worktree${dirtyWorktrees.length === 1 ? " has" : "s have"} uncommitted changes!`,
|
|
1349
|
-
fg: "#
|
|
1418
|
+
fg: "#EF4444",
|
|
1350
1419
|
});
|
|
1351
1420
|
this.confirmContainer.add(warningText);
|
|
1352
1421
|
yOffset += 2;
|
|
1353
1422
|
}
|
|
1354
1423
|
|
|
1355
|
-
//
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
.slice(0, 3);
|
|
1359
|
-
const displayList =
|
|
1360
|
-
branchNames.join(", ") + (worktrees.length > 3 ? `, +${worktrees.length - 3} more` : "");
|
|
1361
|
-
|
|
1362
|
-
const listText = new TextRenderable(this.renderer, {
|
|
1363
|
-
id: "confirm-list",
|
|
1424
|
+
// Info text
|
|
1425
|
+
const infoText = new TextRenderable(this.renderer, {
|
|
1426
|
+
id: "confirm-info",
|
|
1364
1427
|
position: "absolute",
|
|
1365
1428
|
left: 1,
|
|
1366
1429
|
top: yOffset,
|
|
1367
|
-
content:
|
|
1368
|
-
fg: "#
|
|
1430
|
+
content: "This will remove worktree directories from disk.",
|
|
1431
|
+
fg: "#A8A29E",
|
|
1369
1432
|
});
|
|
1370
|
-
this.confirmContainer.add(
|
|
1433
|
+
this.confirmContainer.add(infoText);
|
|
1371
1434
|
yOffset += 2;
|
|
1372
1435
|
|
|
1373
1436
|
// Build options
|
|
@@ -1397,13 +1460,13 @@ class WorktreeSelector {
|
|
|
1397
1460
|
width: 72,
|
|
1398
1461
|
height: 4,
|
|
1399
1462
|
options,
|
|
1400
|
-
backgroundColor: "#
|
|
1401
|
-
focusedBackgroundColor: "#
|
|
1402
|
-
selectedBackgroundColor: "#
|
|
1403
|
-
textColor: "#
|
|
1404
|
-
selectedTextColor: "#
|
|
1405
|
-
descriptionColor: "#
|
|
1406
|
-
selectedDescriptionColor: "#
|
|
1463
|
+
backgroundColor: "#1C1917",
|
|
1464
|
+
focusedBackgroundColor: "#292524",
|
|
1465
|
+
selectedBackgroundColor: "#44403C",
|
|
1466
|
+
textColor: "#E7E5E4",
|
|
1467
|
+
selectedTextColor: "#F87171",
|
|
1468
|
+
descriptionColor: "#A8A29E",
|
|
1469
|
+
selectedDescriptionColor: "#E7E5E4",
|
|
1407
1470
|
showDescription: true,
|
|
1408
1471
|
wrapSelection: true,
|
|
1409
1472
|
});
|