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.
@@ -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.1",
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 const runApp = async (targetPath: string): Promise<void> => {
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 = `Remove: ${branchDisplay}`;
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 ? 10 : 8,
1074
+ height: isDirty ? 12 : 10,
1036
1075
  borderStyle: "single",
1037
- borderColor: "#F59E0B",
1076
+ borderColor: "#EF4444",
1038
1077
  title,
1039
1078
  titleAlignment: "center",
1040
- backgroundColor: "#0F172A",
1079
+ backgroundColor: "#1C1917",
1041
1080
  border: true,
1042
1081
  });
1043
1082
  this.renderer.root.add(this.confirmContainer);
1044
1083
 
1045
- // Warning for dirty worktree
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: "#F59E0B",
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: "#94A3B8",
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: "#0F172A",
1099
- focusedBackgroundColor: "#1E293B",
1100
- selectedBackgroundColor: "#1E3A5F",
1101
- textColor: "#E2E8F0",
1102
- selectedTextColor: "#38BDF8",
1103
- descriptionColor: "#94A3B8",
1104
- selectedDescriptionColor: "#E2E8F0",
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 = `Delete ${count} worktree${count === 1 ? "" : "s"}`;
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 ? 12 : 10,
1380
+ height: hasDirty ? 14 : 12,
1330
1381
  borderStyle: "single",
1331
- borderColor: "#F59E0B",
1382
+ borderColor: "#EF4444",
1332
1383
  title,
1333
1384
  titleAlignment: "center",
1334
- backgroundColor: "#0F172A",
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: "#F59E0B",
1418
+ fg: "#EF4444",
1350
1419
  });
1351
1420
  this.confirmContainer.add(warningText);
1352
1421
  yOffset += 2;
1353
1422
  }
1354
1423
 
1355
- // List worktrees to be deleted
1356
- const branchNames = worktrees
1357
- .map((wt) => wt.branch || basename(wt.path))
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: `Worktrees: ${displayList}`,
1368
- fg: "#94A3B8",
1430
+ content: "This will remove worktree directories from disk.",
1431
+ fg: "#A8A29E",
1369
1432
  });
1370
- this.confirmContainer.add(listText);
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: "#0F172A",
1401
- focusedBackgroundColor: "#1E293B",
1402
- selectedBackgroundColor: "#1E3A5F",
1403
- textColor: "#E2E8F0",
1404
- selectedTextColor: "#38BDF8",
1405
- descriptionColor: "#94A3B8",
1406
- selectedDescriptionColor: "#E2E8F0",
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
  });