@n-seiji/nuthatch 0.1.7 → 0.2.1

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.
@@ -1,26 +1,11 @@
1
1
  import {
2
2
  EXIT_GENERAL_ERROR,
3
3
  EXIT_SAFE_REJECTION
4
- } from "./cli-2bhb9p0x.js";
5
-
6
- // src/domain/posix-path.ts
7
- var withoutTrailingSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
8
- var parentDir = (path) => {
9
- const trimmed = withoutTrailingSlash(path);
10
- const index = trimmed.lastIndexOf("/");
11
- if (index === -1) {
12
- return ".";
13
- }
14
- return index === 0 ? "/" : trimmed.slice(0, index);
15
- };
16
- var baseName = (path) => {
17
- const trimmed = withoutTrailingSlash(path);
18
- return trimmed.slice(trimmed.lastIndexOf("/") + 1);
19
- };
20
- var absoluteCandidates = (dirs, name) => [
21
- ...new Set(dirs.map((entry) => entry.trim()).filter((entry) => entry.startsWith("/")).map((dir) => `${dir.endsWith("/") ? dir.slice(0, -1) : dir}/${name}`))
22
- ];
23
- var splitPathEnv = (pathEnv) => (pathEnv ?? "").split(":");
4
+ } from "./cli-77yanq37.js";
5
+ import {
6
+ absoluteCandidates,
7
+ splitPathEnv
8
+ } from "./cli-s5qcjcwk.js";
24
9
 
25
10
  // src/domain/git-executable.ts
26
11
  var GIT_EXECUTABLE_OVERRIDE_ENV = "HOP_GIT";
@@ -66,4 +51,4 @@ var describeFatalError = (error) => {
66
51
  return { message: messageOf(error), exitCode: EXIT_GENERAL_ERROR };
67
52
  };
68
53
 
69
- export { parentDir, baseName, absoluteCandidates, splitPathEnv, GIT_EXECUTABLE_OVERRIDE_ENV, GIT_NOT_FOUND_ERROR_CODE, gitExecutableCandidates, gitNotFoundMessage, messageOf, describeFatalError };
54
+ export { GIT_EXECUTABLE_OVERRIDE_ENV, GIT_NOT_FOUND_ERROR_CODE, gitExecutableCandidates, gitNotFoundMessage, messageOf, describeFatalError };
@@ -1,29 +1,39 @@
1
- import"./cli-qrxgkskr.js";
1
+ import"./cli-ph0gp56k.js";
2
2
  import {
3
3
  rm2
4
- } from "./cli-23z98dw1.js";
4
+ } from "./cli-9n04gsq1.js";
5
5
  import {
6
6
  root2
7
- } from "./cli-ygt2w4e1.js";
7
+ } from "./cli-hz0r7zw8.js";
8
8
  import {
9
9
  graphemes,
10
10
  displayWidth,
11
11
  padToWidth,
12
12
  truncateToWidth,
13
- truncateToWidthKeepingTail
14
- } from "./cli-71jgs14h.js";
13
+ truncateToWidthKeepingTail,
14
+ ESC,
15
+ SGR,
16
+ SGR_RESET
17
+ } from "./cli-fhta3tr8.js";
18
+ import {
19
+ colorEnabled
20
+ } from "./cli-26d2drt6.js";
15
21
  import {
16
22
  render,
17
23
  reportResult
18
24
  } from "./cli-dbzx9nzg.js";
19
25
  import {
26
+ isOptionLikeRef,
20
27
  loadRepoContext,
21
28
  worktreeDirtyState
22
- } from "./cli-7jg5qqs9.js";
29
+ } from "./cli-y8vsqre6.js";
23
30
  import {
24
31
  EXIT_CANCELLED,
25
32
  ok
26
- } from "./cli-2bhb9p0x.js";
33
+ } from "./cli-77yanq37.js";
34
+ import {
35
+ shortenHome
36
+ } from "./cli-s5qcjcwk.js";
27
37
 
28
38
  // src/domain/candidates.ts
29
39
  var buildPickCandidates = (worktrees, dirtyByPath, localBranches, remoteBranches) => {
@@ -33,9 +43,10 @@ var buildPickCandidates = (worktrees, dirtyByPath, localBranches, remoteBranches
33
43
  worktree,
34
44
  dirty: dirtyByPath.get(worktree.path) ?? null
35
45
  }));
36
- const localOnly = localBranches.filter((branch) => !existingBranches.has(branch));
46
+ const usable = (branch) => !existingBranches.has(branch) && !isOptionLikeRef(branch);
47
+ const localOnly = localBranches.filter((branch) => usable(branch));
37
48
  const localSet = new Set(localBranches);
38
- const remoteOnly = remoteBranches.filter((branch) => !existingBranches.has(branch) && !localSet.has(branch));
49
+ const remoteOnly = remoteBranches.filter((branch) => usable(branch) && !localSet.has(branch));
39
50
  return [
40
51
  ...worktreeCandidates,
41
52
  ...localOnly.map((branch) => ({
@@ -72,30 +83,16 @@ var pick = async (git, fs, options) => {
72
83
  // src/ui/picker-render.ts
73
84
  import { homedir } from "node:os";
74
85
 
75
- // src/ui/ansi.ts
76
- var ESC = "\x1B";
77
-
78
86
  // src/ui/picker-frame.ts
79
87
  var CURSOR_HOME = `${ESC}[H`;
80
88
  var CLEAR_TO_END = `${ESC}[J`;
81
- var SGR_RESET = `${ESC}[0m`;
82
- var SGR_CODES = {
83
- plain: "",
84
- cyan: `${ESC}[36m`,
85
- dim: `${ESC}[2m`,
86
- "bold-dim": `${ESC}[1m${ESC}[2m`,
87
- inverse: `${ESC}[7m`,
88
- red: `${ESC}[31m`,
89
- green: `${ESC}[32m`,
90
- yellow: `${ESC}[33m`,
91
- magenta: `${ESC}[35m`
92
- };
89
+ var sgrCode = (style) => style === "bold-dim" ? `${SGR.bold}${SGR.dim}` : SGR[style];
93
90
  var renderSpan = (span, colorEnabled) => {
94
91
  const style = span.style ?? "plain";
95
92
  if (!colorEnabled || style === "plain") {
96
93
  return span.text;
97
94
  }
98
- return `${SGR_CODES[style]}${span.text}${SGR_RESET}`;
95
+ return `${sgrCode(style)}${span.text}${SGR_RESET}`;
99
96
  };
100
97
  var lineWidth = (line) => line.reduce((total, span) => total + displayWidth(span.text), 0);
101
98
  var renderLine = (line, colorEnabled) => line.map((span) => renderSpan(span, colorEnabled)).join("");
@@ -268,20 +265,16 @@ var sortCandidatesForDisplay = (candidates) => [
268
265
  ...candidates.filter((candidate) => isCreatableCandidate(candidate)).toSorted((a, b) => compareCreatableCandidates(a, b))
269
266
  ];
270
267
  var KIND_COLUMN_WIDTH = Math.max(...Object.values(WORKTREE_KIND_LABELS).map((label) => label.length), ...Object.values(CREATABLE_SOURCE_LABELS).map((label) => label.length));
271
- var shortenPath = (path, homeDir, maxLength = MAX_PATH_LENGTH) => {
272
- const withTilde = homeDir.length > 0 && (path === homeDir || path.startsWith(`${homeDir}/`)) ? `~${path.slice(homeDir.length)}` : path;
273
- return truncateToWidthKeepingTail(withTilde, maxLength);
274
- };
268
+ var shortenPath = (path, homeDir, maxLength = MAX_PATH_LENGTH) => truncateToWidthKeepingTail(shortenHome(path, homeDir), maxLength);
275
269
  var candidatePathLabel = (candidate, homeDir, maxLength) => candidate.kind === "worktree" ? shortenPath(candidate.worktree.path, homeDir, maxLength) : "";
276
270
  var rawBranchColumnWidth = (candidates) => candidates.reduce((max, candidate) => Math.max(max, displayWidth(candidateBranchLabel(candidate))), 0);
277
271
  var branchColumnWidth = (candidates) => Math.min(MAX_BRANCH_COLUMN_WIDTH, rawBranchColumnWidth(candidates));
278
- var padBranchLabel = (label, width) => padToWidth(label, width);
279
272
  var toCandidateRow = (candidate, options) => ({
280
273
  kind: "candidate",
281
274
  index: options.index,
282
275
  section: options.section,
283
276
  statusMarker: statusMarker(candidate),
284
- branchLabel: padBranchLabel(truncateToWidth(candidateBranchLabel(candidate), options.branchWidth), options.branchWidth),
277
+ branchLabel: padToWidth(truncateToWidth(candidateBranchLabel(candidate), options.branchWidth), options.branchWidth),
285
278
  kindLabel: candidateKindLabel(candidate).padEnd(KIND_COLUMN_WIDTH, " "),
286
279
  pathLabel: candidatePathLabel(candidate, options.homeDir, options.pathMaxLength)
287
280
  });
@@ -575,8 +568,6 @@ var renderPickerFrame = ({
575
568
  };
576
569
 
577
570
  // src/ui/picker-keys.ts
578
- var isCtrlJByte = (input) => input === `
579
- `;
580
571
  var resolvePickerKeyAction = (input, key) => {
581
572
  if (key.escape) {
582
573
  return { type: "cancel", reason: "esc" };
@@ -599,7 +590,7 @@ var resolvePickerKeyAction = (input, key) => {
599
590
  if (key.upArrow || key.ctrl && (input === "p" || input === "k")) {
600
591
  return { type: "up" };
601
592
  }
602
- if (key.downArrow || key.ctrl && (input === "n" || input === "j") || isCtrlJByte(input)) {
593
+ if (key.downArrow || key.ctrl && input === "n") {
603
594
  return { type: "down" };
604
595
  }
605
596
  if (key.ctrl && input === "u") {
@@ -608,7 +599,7 @@ var resolvePickerKeyAction = (input, key) => {
608
599
  if (key.backspace || key.delete) {
609
600
  return { type: "backspace" };
610
601
  }
611
- if (input.length > 0 && !key.ctrl && !key.meta) {
602
+ if (input.length > 0 && !key.ctrl) {
612
603
  return { type: "char", char: input };
613
604
  }
614
605
  return { type: "ignore" };
@@ -624,17 +615,17 @@ var resolvePanelKeyAction = (input, key) => {
624
615
  if (key.upArrow || key.ctrl && (input === "p" || input === "k")) {
625
616
  return { type: "up" };
626
617
  }
627
- if (key.downArrow || key.ctrl && (input === "n" || input === "j") || isCtrlJByte(input)) {
618
+ if (key.downArrow || key.ctrl && input === "n") {
628
619
  return { type: "down" };
629
620
  }
630
- if (!key.ctrl && !key.meta && isPanelLetterShortcut(input)) {
621
+ if (!key.ctrl && isPanelLetterShortcut(input)) {
631
622
  return { type: "letter", char: input };
632
623
  }
633
624
  return { type: "ignore" };
634
625
  };
635
626
  var isPanelLetterShortcut = (input) => PANEL_LETTER_SHORTCUTS.includes(input);
636
627
  var resolveConfirmKeyAction = (input, key) => {
637
- if (!key.ctrl && !key.meta && (input === "y" || input === "Y")) {
628
+ if (!key.ctrl && (input === "y" || input === "Y")) {
638
629
  return { type: "yes" };
639
630
  }
640
631
  return { type: "no" };
@@ -789,20 +780,14 @@ var createFilteredSelector = () => {
789
780
  var runMutation = async (callbacks, candidate, action) => {
790
781
  if (action === "switchRoot") {
791
782
  const result = await callbacks.switchRootHere(candidate);
792
- if (!result.ok || result.path === undefined) {
793
- return {
794
- type: "patch",
795
- patch: panelErrorTransition(candidate, result.message ?? "Failed to switch root.")
796
- };
783
+ if (!result.ok) {
784
+ return { type: "patch", patch: panelErrorTransition(candidate, result.message) };
797
785
  }
798
786
  return { type: "exit", outcome: { type: "path", path: result.path } };
799
787
  }
800
788
  const result = await callbacks.deleteWorktree(candidate);
801
789
  if (!result.ok) {
802
- return {
803
- type: "patch",
804
- patch: panelErrorTransition(candidate, result.message ?? "Failed to delete worktree.")
805
- };
790
+ return { type: "patch", patch: panelErrorTransition(candidate, result.message) };
806
791
  }
807
792
  const fresh = await callbacks.reloadCandidates();
808
793
  return {
@@ -953,7 +938,6 @@ var leaveAltScreen = (target) => {
953
938
  // src/ui/picker-key-event.ts
954
939
  var NO_MODIFIERS = {
955
940
  ctrl: false,
956
- meta: false,
957
941
  escape: false,
958
942
  return: false,
959
943
  tab: false,
@@ -968,89 +952,40 @@ var event = (input, key = {}) => ({
968
952
  input,
969
953
  key: { ...NO_MODIFIERS, ...key }
970
954
  });
971
- var withMeta = (keyEvent) => ({
972
- input: keyEvent.input,
973
- key: { ...keyEvent.key, meta: true }
974
- });
975
955
 
976
956
  // src/ui/picker-key-csi.ts
977
- var ARROW_FINAL_BYTE_A = "A".codePointAt(0);
978
- var ARROW_FINAL_BYTE_B = "B".codePointAt(0);
979
- var ARROW_FINAL_BYTE_C = "C".codePointAt(0);
980
- var ARROW_FINAL_BYTE_D = "D".codePointAt(0);
981
- var arrowModifierKey = (finalByte) => {
982
- if (finalByte === ARROW_FINAL_BYTE_A) {
983
- return "upArrow";
984
- }
985
- if (finalByte === ARROW_FINAL_BYTE_B) {
986
- return "downArrow";
987
- }
988
- if (finalByte === ARROW_FINAL_BYTE_C) {
989
- return "rightArrow";
990
- }
991
- if (finalByte === ARROW_FINAL_BYTE_D) {
992
- return "leftArrow";
993
- }
994
- return null;
995
- };
996
- var modifierBitsFromParam = (param) => {
997
- const bits = param - 1;
998
- const CTRL_BIT = 4;
999
- const ALT_BIT = 2;
1000
- return { ctrl: (bits & CTRL_BIT) !== 0, meta: (bits & ALT_BIT) !== 0 };
1001
- };
957
+ var ARROW_KEYS = new Map([
958
+ ["A", "upArrow"],
959
+ ["B", "downArrow"],
960
+ ["C", "rightArrow"],
961
+ ["D", "leftArrow"]
962
+ ]);
1002
963
  var CSI_FINAL_BYTE_MIN = 64;
1003
964
  var CSI_FINAL_BYTE_MAX = 126;
1004
965
  var isCsiFinalByte = (byte) => byte >= CSI_FINAL_BYTE_MIN && byte <= CSI_FINAL_BYTE_MAX;
1005
- var CTRL_C_BYTE = 3;
1006
- var BRACKETED_PASTE_START = "200~";
1007
- var BRACKETED_PASTE_END = "201~";
1008
- var modifiedArrowOrDelete = (paramText, finalByte) => {
1009
- const MODIFIED_PATTERN = /^\d+;\d+$/u;
1010
- const DELETE_PARAM = "3";
1011
- if (finalByte === "~" && (paramText === DELETE_PARAM || paramText.startsWith(`${DELETE_PARAM};`))) {
1012
- return event("", { delete: true });
1013
- }
1014
- const arrowKey = arrowModifierKey(finalByte.codePointAt(0));
1015
- if (arrowKey === null || !(paramText === "" || MODIFIED_PATTERN.test(paramText))) {
1016
- return null;
966
+ var DELETE_PARAM = "3";
967
+ var csiEvent = (paramText, finalByte) => {
968
+ if (finalByte === "~") {
969
+ const [first] = paramText.split(";");
970
+ return first === DELETE_PARAM ? event("", { delete: true }) : null;
1017
971
  }
1018
- const [, modifierParam] = paramText.split(";");
1019
- const mods = modifierParam === undefined ? {} : modifierBitsFromParam(Number(modifierParam));
1020
- return event("", { [arrowKey]: true, ...mods });
972
+ const arrowKey = ARROW_KEYS.get(finalByte);
973
+ return arrowKey === undefined ? null : event("", { [arrowKey]: true });
1021
974
  };
1022
975
  var parseCsi = (buf) => {
1023
976
  const CSI_PREFIX_LENGTH = 2;
1024
977
  let index = CSI_PREFIX_LENGTH;
1025
- let paramText = "";
1026
978
  while (index < buf.length && !isCsiFinalByte(buf[index])) {
1027
- const paramByte = buf[index];
1028
- if (paramByte === CTRL_C_BYTE) {
1029
- return {
1030
- kind: "interrupted",
1031
- consumed: index + 1,
1032
- event: event("c", { ctrl: true })
1033
- };
1034
- }
1035
- paramText += String.fromCodePoint(paramByte);
1036
979
  index += 1;
1037
980
  }
1038
981
  if (index >= buf.length) {
1039
982
  return { kind: "incomplete" };
1040
983
  }
1041
- const finalByte = String.fromCodePoint(buf[index]);
1042
- const consumed = index + 1;
1043
- const full = paramText + finalByte;
1044
- if (full === BRACKETED_PASTE_START) {
1045
- return { kind: "resolved", consumed, event: null, pasteStarted: true };
1046
- }
1047
- if (full === BRACKETED_PASTE_END) {
1048
- return { kind: "resolved", consumed, event: null, pasteEnded: true };
1049
- }
984
+ const paramText = buf.subarray(CSI_PREFIX_LENGTH, index).toString("latin1");
1050
985
  return {
1051
986
  kind: "resolved",
1052
- consumed,
1053
- event: modifiedArrowOrDelete(paramText, finalByte)
987
+ consumed: index + 1,
988
+ event: csiEvent(paramText, String.fromCodePoint(buf[index]))
1054
989
  };
1055
990
  };
1056
991
  var SS3_SEQUENCE_LENGTH = 3;
@@ -1058,19 +993,11 @@ var parseSs3 = (buf) => {
1058
993
  if (buf.length < SS3_SEQUENCE_LENGTH) {
1059
994
  return { kind: "incomplete" };
1060
995
  }
1061
- const finalByte = buf[2];
1062
- if (finalByte === CTRL_C_BYTE) {
1063
- return {
1064
- kind: "interrupted",
1065
- consumed: SS3_SEQUENCE_LENGTH,
1066
- event: event("c", { ctrl: true })
1067
- };
1068
- }
1069
- const arrowKey = arrowModifierKey(finalByte);
996
+ const arrowKey = ARROW_KEYS.get(String.fromCodePoint(buf[2]));
1070
997
  return {
1071
998
  kind: "resolved",
1072
999
  consumed: SS3_SEQUENCE_LENGTH,
1073
- event: arrowKey === null ? null : event("", { [arrowKey]: true })
1000
+ event: arrowKey === undefined ? null : event("", { [arrowKey]: true })
1074
1001
  };
1075
1002
  };
1076
1003
 
@@ -1124,13 +1051,11 @@ var isPrintableChar = (char) => {
1124
1051
  return codePoint < C1_CONTROL_MIN || codePoint > C1_CONTROL_MAX;
1125
1052
  };
1126
1053
  var pastedTextToEvents = (text) => [...text].filter((char) => isPrintableChar(char)).map((char) => event(char));
1054
+ var PASTE_START_MARKER = Buffer.from("\x1B[200~", "latin1");
1055
+ var PASTE_END_MARKER = Buffer.from("\x1B[201~", "latin1");
1127
1056
  var CSI_INTRODUCER_CODE = "[".codePointAt(0);
1128
1057
  var SS3_INTRODUCER_CODE = "O".codePointAt(0);
1129
1058
  var MAX_PENDING_SEQUENCE_BYTES = 32;
1130
- var PASTE_END_MARKER = Buffer.concat([
1131
- Buffer.from([ESC2]),
1132
- Buffer.from(`[${BRACKETED_PASTE_END}`)
1133
- ]);
1134
1059
 
1135
1060
  class PickerKeyParser {
1136
1061
  buffer = Buffer.alloc(0);
@@ -1166,11 +1091,9 @@ class PickerKeyParser {
1166
1091
  this.inPaste = false;
1167
1092
  return pastedTextToEvents(text);
1168
1093
  }
1169
- const maxOverlap = Math.min(PASTE_END_MARKER.length - 1, this.buffer.length);
1170
1094
  let overlap = 0;
1171
- for (let candidate = maxOverlap;candidate > 0; candidate -= 1) {
1172
- const tail = this.buffer.subarray(this.buffer.length - candidate);
1173
- if (tail.equals(PASTE_END_MARKER.subarray(0, candidate))) {
1095
+ for (let candidate = Math.min(PASTE_END_MARKER.length - 1, this.buffer.length);candidate > 0; candidate -= 1) {
1096
+ if (this.buffer.subarray(this.buffer.length - candidate).equals(PASTE_END_MARKER.subarray(0, candidate))) {
1174
1097
  overlap = candidate;
1175
1098
  break;
1176
1099
  }
@@ -1197,8 +1120,7 @@ class PickerKeyParser {
1197
1120
  }
1198
1121
  if (first === LF) {
1199
1122
  this.buffer = this.buffer.subarray(1);
1200
- return [event(`
1201
- `)];
1123
+ return [];
1202
1124
  }
1203
1125
  if (first === TAB) {
1204
1126
  this.buffer = this.buffer.subarray(1);
@@ -1224,14 +1146,18 @@ class PickerKeyParser {
1224
1146
  }
1225
1147
  const second = this.buffer[1];
1226
1148
  if (second === CSI_INTRODUCER_CODE) {
1149
+ if (this.buffer.subarray(0, PASTE_START_MARKER.length).equals(PASTE_START_MARKER)) {
1150
+ this.buffer = this.buffer.subarray(PASTE_START_MARKER.length);
1151
+ this.inPaste = true;
1152
+ return [];
1153
+ }
1227
1154
  return this.applySequence(parseCsi(this.buffer));
1228
1155
  }
1229
1156
  if (second === SS3_INTRODUCER_CODE) {
1230
1157
  return this.applySequence(parseSs3(this.buffer));
1231
1158
  }
1232
1159
  this.buffer = this.buffer.subarray(1);
1233
- const inner = this.stepUtf8Char();
1234
- return inner === null ? null : inner.map((keyEvent) => withMeta(keyEvent));
1160
+ return [event("", { escape: true })];
1235
1161
  }
1236
1162
  applySequence(result) {
1237
1163
  if (result.kind === "incomplete") {
@@ -1240,17 +1166,7 @@ class PickerKeyParser {
1240
1166
  }
1241
1167
  return null;
1242
1168
  }
1243
- if (result.kind === "interrupted") {
1244
- this.buffer = this.buffer.subarray(result.consumed);
1245
- return [result.event];
1246
- }
1247
1169
  this.buffer = this.buffer.subarray(result.consumed);
1248
- if (result.pasteStarted === true) {
1249
- this.inPaste = true;
1250
- }
1251
- if (result.pasteEnded === true) {
1252
- this.inPaste = false;
1253
- }
1254
1170
  return result.event === null ? [] : [result.event];
1255
1171
  }
1256
1172
  stepUtf8Char() {
@@ -1432,7 +1348,6 @@ var runTerminalSession = (createHandlers, streams = defaultStreams()) => new Pro
1432
1348
  // src/ui/picker.ts
1433
1349
  var DEFAULT_TERMINAL_WIDTH = 80;
1434
1350
  var terminalDimension = (value, fallback) => value !== undefined && value > 0 ? value : fallback;
1435
- var colorEnabled = (stderr) => stderr.isTTY === true && (process.env["NO_COLOR"] ?? "") === "";
1436
1351
  var isCtrlC = (event) => event.key.ctrl && event.input === "c";
1437
1352
  var runPicker = (candidates, callbacks) => runTerminalSession(({ requestRender, finish }) => {
1438
1353
  const store = createPickerStore(candidates, callbacks, (outcome) => finish(outcome), (reason) => finish({ type: "cancelled", reason }));
@@ -1473,8 +1388,7 @@ var deleteWorktree = async (git, fs, candidate) => {
1473
1388
  branch,
1474
1389
  ...candidate.kind === "worktree" ? { expectedPath: candidate.worktree.path } : {},
1475
1390
  allowPrunable: candidate.kind === "worktree" && candidate.worktree.prunable,
1476
- force: false,
1477
- ext: false
1391
+ force: false
1478
1392
  });
1479
1393
  if (!result.ok) {
1480
1394
  return {
@@ -1500,7 +1414,7 @@ var createPickerCallbacks = (git, fs, json, onSwitchedBranch) => ({
1500
1414
  allowExternalHolderSwap: candidate.kind === "worktree" && candidate.worktree.kind === "external",
1501
1415
  ...candidate.kind === "worktree" ? { expectedHolderPath: candidate.worktree.path } : {}
1502
1416
  });
1503
- if (!result.ok) {
1417
+ if (!result.ok || result.path === undefined) {
1504
1418
  return {
1505
1419
  ok: false,
1506
1420
  message: result.errorMessage ?? "Failed to switch root."
@@ -1511,10 +1425,7 @@ var createPickerCallbacks = (git, fs, json, onSwitchedBranch) => ({
1511
1425
  detachedHolder: result.data?.detachedHolder ?? null,
1512
1426
  warnings: result.warnings ?? []
1513
1427
  });
1514
- return {
1515
- ok: true,
1516
- ...result.path === undefined ? {} : { path: result.path }
1517
- };
1428
+ return { ok: true, path: result.path };
1518
1429
  },
1519
1430
  reloadCandidates: async () => await loadPickCandidates(git, fs, json) ?? []
1520
1431
  });
@@ -0,0 +1,25 @@
1
+ // src/domain/posix-path.ts
2
+ var withoutTrailingSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
3
+ var shortenHome = (path, home) => {
4
+ if (home === "" || home === "/") {
5
+ return path;
6
+ }
7
+ if (path === home) {
8
+ return "~";
9
+ }
10
+ return path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
11
+ };
12
+ var parentDir = (path) => {
13
+ const trimmed = withoutTrailingSlash(path);
14
+ const index = trimmed.lastIndexOf("/");
15
+ if (index === -1) {
16
+ return ".";
17
+ }
18
+ return index === 0 ? "/" : trimmed.slice(0, index);
19
+ };
20
+ var absoluteCandidates = (dirs, name) => [
21
+ ...new Set(dirs.map((entry) => entry.trim()).filter((entry) => entry.startsWith("/")).map((dir) => `${dir.endsWith("/") ? dir.slice(0, -1) : dir}/${name}`))
22
+ ];
23
+ var splitPathEnv = (pathEnv) => (pathEnv ?? "").split(":");
24
+
25
+ export { shortenHome, parentDir, absoluteCandidates, splitPathEnv };
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  describeFatalError
3
- } from "./cli-qrxgkskr.js";
3
+ } from "./cli-ph0gp56k.js";
4
4
  import {
5
5
  reportResult
6
6
  } from "./cli-dbzx9nzg.js";
7
7
  import {
8
8
  fail
9
- } from "./cli-2bhb9p0x.js";
9
+ } from "./cli-77yanq37.js";
10
10
 
11
11
  // src/cli-dispatch.ts
12
12
  var normalizeCliArgs = (rawArgs, argv0, isCompiledBinary) => isCompiledBinary && rawArgs.length === 1 && rawArgs[0] === argv0 ? [] : rawArgs;
@@ -37,7 +37,7 @@ var reportFatalError = (command, json, error) => {
37
37
  // package.json
38
38
  var package_default = {
39
39
  name: "@n-seiji/nuthatch",
40
- version: "0.1.7",
40
+ version: "0.2.1",
41
41
  description: "git worktree manager (hop)",
42
42
  homepage: "https://github.com/n-seiji/nuthatch#readme",
43
43
  bugs: {
@@ -52,8 +52,7 @@ var package_default = {
52
52
  hop: "dist/cli.js"
53
53
  },
54
54
  files: [
55
- "dist",
56
- "shell"
55
+ "dist"
57
56
  ],
58
57
  type: "module",
59
58
  publishConfig: {
@@ -73,7 +72,7 @@ var package_default = {
73
72
  },
74
73
  devDependencies: {
75
74
  "@types/bun": "latest",
76
- oxfmt: "^0.66.0",
75
+ oxfmt: "^0.72.0",
77
76
  oxlint: "^1.81.0",
78
77
  typescript: "^5.6.0"
79
78
  },
@@ -85,30 +84,4 @@ var package_default = {
85
84
  // src/version.ts
86
85
  var VERSION = package_default.version;
87
86
 
88
- // src/infra/executable.ts
89
- import { constants } from "node:fs";
90
- import { access, stat } from "node:fs/promises";
91
- var isExecutableFile = async (path) => {
92
- try {
93
- const stats = await stat(path);
94
- if (!stats.isFile()) {
95
- return false;
96
- }
97
- await access(path, constants.X_OK);
98
- return true;
99
- } catch {
100
- return false;
101
- }
102
- };
103
- var firstExecutable = async (candidates) => {
104
- const probes = candidates.map(async (candidate) => await isExecutableFile(candidate) ? candidate : null);
105
- for (const probe of probes) {
106
- const found = await probe;
107
- if (found !== null) {
108
- return found;
109
- }
110
- }
111
- return null;
112
- };
113
-
114
- export { normalizeCliArgs, isRunningAsCompiledBinary, isUpdateRequest, isHelpRequest, isVersionRequest, dispatchCliArgs, wantsJson, reportFatalError, firstExecutable, VERSION };
87
+ export { normalizeCliArgs, isRunningAsCompiledBinary, isUpdateRequest, isHelpRequest, isVersionRequest, dispatchCliArgs, wantsJson, reportFatalError, VERSION };