@ogpoyraz/wtx 0.9.2 → 0.9.4

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/README.md CHANGED
@@ -96,7 +96,7 @@ If origin already has a branch with that name owned by someone else, `wtx` warns
96
96
  | `stack` | `<branch>` | `--repo`, `--json` | Show recorded parent and descendant branches |
97
97
  | `prs` | | `--repo`, `--json`, `--all` | Pull request status across worktrees |
98
98
  | `exec` | `<branch> <command...>` | `--repo` | Run a command inside a worktree (`WTX_PORT` injected) |
99
- | `terminal` | | | Interactive worktree dashboard (requires Bun) |
99
+ | `terminal` | | `--wo-details` | Interactive worktree dashboard (requires Bun) |
100
100
  | `mcp` | | | Run MCP server exposing worktree tools over stdio |
101
101
  | `cd` | `<repo> <branch>` | | cd into worktree (requires shell integration) |
102
102
  | `history` | | `--limit`, `--json`, `--source` | Show recent action history (`~/.local/state/wtx/history.jsonl`) |
@@ -304,6 +304,7 @@ Stack metadata is stored under the repository's common Git directory and is not
304
304
 
305
305
  Launch the TUI with `wtx terminal` (requires [Bun](https://bun.sh)). Navigation never locks, and actions run in the background.
306
306
  `wtx terminal` opens a full-screen dashboard across all configured repos (requires the [Bun](https://bun.sh) runtime).
307
+ Pass `--wo-details` for a repositories-only view: the right pane (Details, Changes, terminal sessions) is hidden and the worktree list takes the full width.
307
308
 
308
309
  | Key | Action |
309
310
  |---|---|
@@ -73,6 +73,7 @@ _wtx() {
73
73
  'prs[Show pull request status across worktrees]' \
74
74
  'init[Output shell integration code]' \
75
75
  'skill[Manage AI agent skills]' \
76
+ 'terminal[Interactive terminal dashboard]' \
76
77
  'mcp[Run MCP server exposing worktree tools]' \
77
78
  'exec[Execute commands across worktrees]'
78
79
  ;;
@@ -227,6 +228,13 @@ _wtx() {
227
228
  '--verbose[Show git commands]' \
228
229
  '--dry-run[Show what would happen]'
229
230
  ;;
231
+ terminal)
232
+ _arguments \
233
+ '--wo-details[Repositories panel only (hide details/right pane)]' \
234
+ {-q,--quiet}'[Suppress output]' \
235
+ '--verbose[Show git commands]' \
236
+ '--dry-run[Show what would happen]'
237
+ ;;
230
238
  esac
231
239
  ;;
232
240
  esac
@@ -102,6 +102,7 @@ _wtx_completions() {
102
102
  status) flags="-r --repo --base --json -q --quiet --verbose --dry-run --help" ;;
103
103
  stack) flags="-r --repo --json -q --quiet --verbose --dry-run --help" ;;
104
104
  prs) flags="-r --repo --json --all -q --quiet --verbose --dry-run --help" ;;
105
+ terminal) flags="--wo-details -q --quiet --verbose --dry-run --help" ;;
105
106
  config)
106
107
  case "$subsubcommand" in
107
108
  add-repo) flags="--sync-files --post-create --post-sync --help" ;;
@@ -99,6 +99,9 @@ complete -c wtx -n "__fish_seen_subcommand_from ls" -l pr -d "Include pull reque
99
99
  # prs flags
100
100
  complete -c wtx -n "__fish_seen_subcommand_from prs" -l all -d "Include drafts and closed/merged PRs"
101
101
 
102
+ # terminal flags
103
+ complete -c wtx -n "__fish_seen_subcommand_from terminal" -l wo-details -d "Repositories panel only (hide details/right pane)"
104
+
102
105
  # stack-aware flags
103
106
  complete -c wtx -n "__fish_seen_subcommand_from rebase" -l onto -xa "main master develop dev" -d "Override recorded base"
104
107
  complete -c wtx -n "__fish_seen_subcommand_from status" -l base -xa "main master develop dev" -d "Override recorded base"
package/dist/cli.mjs CHANGED
@@ -25068,7 +25068,7 @@ var init_owner = __esm(() => {
25068
25068
  });
25069
25069
 
25070
25070
  // src/lib/workspace.ts
25071
- import fs23 from "fs";
25071
+ import fs24 from "fs";
25072
25072
  import path29 from "path";
25073
25073
  function getWorkspaceRoot(config2) {
25074
25074
  return path29.resolve(expandTilde(config2.workspace_root ?? path29.join(config2.root, "wtx-workspaces")));
@@ -25119,12 +25119,12 @@ function assertNoPathNesting(workspacePath, members) {
25119
25119
  async function writeManifest(workspacePath, manifest) {
25120
25120
  const manifestPath = path29.join(workspacePath, MANIFEST_FILE);
25121
25121
  const tmpPath = path29.join(workspacePath, `${MANIFEST_FILE}.${process.pid}.${Date.now()}.tmp`);
25122
- await fs23.promises.writeFile(tmpPath, `${JSON.stringify(manifest, null, 2)}
25122
+ await fs24.promises.writeFile(tmpPath, `${JSON.stringify(manifest, null, 2)}
25123
25123
  `, "utf8");
25124
- await fs23.promises.rename(tmpPath, manifestPath);
25124
+ await fs24.promises.rename(tmpPath, manifestPath);
25125
25125
  }
25126
25126
  async function readManifest(workspacePath) {
25127
- const raw = await fs23.promises.readFile(path29.join(workspacePath, MANIFEST_FILE), "utf8");
25127
+ const raw = await fs24.promises.readFile(path29.join(workspacePath, MANIFEST_FILE), "utf8");
25128
25128
  const parsed = JSON.parse(raw);
25129
25129
  if (parsed.version !== 1) {
25130
25130
  throw new Error(`Unsupported workspace manifest version: ${parsed.version}`);
@@ -25139,7 +25139,7 @@ async function writeAgentsFile(workspacePath, linkNames) {
25139
25139
  ...linkNames.map((name) => `- ${name}`),
25140
25140
  ""
25141
25141
  ];
25142
- await fs23.promises.writeFile(path29.join(workspacePath, AGENTS_FILE), lines.join(`
25142
+ await fs24.promises.writeFile(path29.join(workspacePath, AGENTS_FILE), lines.join(`
25143
25143
  `), "utf8");
25144
25144
  }
25145
25145
  async function createMemberSymlinks(workspacePath, members) {
@@ -25149,13 +25149,13 @@ async function createMemberSymlinks(workspacePath, members) {
25149
25149
  const linkName = memberLinkName(member, taken);
25150
25150
  const linkPath = path29.join(workspacePath, linkName);
25151
25151
  const target = path29.resolve(member.path);
25152
- await fs23.promises.symlink(target, linkPath, process.platform === "win32" ? "junction" : "dir");
25152
+ await fs24.promises.symlink(target, linkPath, process.platform === "win32" ? "junction" : "dir");
25153
25153
  linkNames.push(linkName);
25154
25154
  }
25155
25155
  return linkNames;
25156
25156
  }
25157
25157
  async function workspaceLinkNames(workspacePath) {
25158
- const entries = await fs23.promises.readdir(workspacePath, { withFileTypes: true });
25158
+ const entries = await fs24.promises.readdir(workspacePath, { withFileTypes: true });
25159
25159
  return entries.filter((entry) => entry.name !== MANIFEST_FILE && entry.name !== AGENTS_FILE && entry.isSymbolicLink()).map((entry) => entry.name).sort();
25160
25160
  }
25161
25161
  async function rebuildAgentsFile(workspacePath) {
@@ -25165,7 +25165,7 @@ async function createWorkspace(opts) {
25165
25165
  validateWorkspaceName(opts.name);
25166
25166
  const workspacePath = workspacePathFor(opts.name, opts.config);
25167
25167
  assertNoPathNesting(workspacePath, opts.members);
25168
- await fs23.promises.mkdir(workspacePath, { recursive: true });
25168
+ await fs24.promises.mkdir(workspacePath, { recursive: true });
25169
25169
  const linkNames = await createMemberSymlinks(workspacePath, opts.members);
25170
25170
  await writeManifest(workspacePath, {
25171
25171
  version: 1,
@@ -25183,7 +25183,7 @@ async function addMember(opts) {
25183
25183
  const taken = new Set(await workspaceLinkNames(opts.workspacePath));
25184
25184
  const linkName = memberLinkName(opts.member, taken);
25185
25185
  const target = path29.resolve(opts.member.path);
25186
- await fs23.promises.symlink(target, path29.join(opts.workspacePath, linkName), process.platform === "win32" ? "junction" : "dir");
25186
+ await fs24.promises.symlink(target, path29.join(opts.workspacePath, linkName), process.platform === "win32" ? "junction" : "dir");
25187
25187
  manifest.members.push({ repo: opts.member.repo, branch: opts.member.branch });
25188
25188
  await writeManifest(opts.workspacePath, manifest);
25189
25189
  await rebuildAgentsFile(opts.workspacePath);
@@ -25197,7 +25197,7 @@ async function removeMember(opts) {
25197
25197
  const linkNames = await workspaceLinkNames(opts.workspacePath);
25198
25198
  for (const linkName of linkNames) {
25199
25199
  if (linkName === opts.repo || linkName === `${opts.repo}-${opts.branch.replace(/[\\/]+/g, "-")}`) {
25200
- await fs23.promises.unlink(path29.join(opts.workspacePath, linkName));
25200
+ await fs24.promises.unlink(path29.join(opts.workspacePath, linkName));
25201
25201
  break;
25202
25202
  }
25203
25203
  }
@@ -25206,7 +25206,7 @@ async function removeMember(opts) {
25206
25206
  }
25207
25207
  async function listWorkspaces(workspaceRoot) {
25208
25208
  try {
25209
- const entries = await fs23.promises.readdir(path29.resolve(expandTilde(workspaceRoot)), { withFileTypes: true });
25209
+ const entries = await fs24.promises.readdir(path29.resolve(expandTilde(workspaceRoot)), { withFileTypes: true });
25210
25210
  const workspaces = [];
25211
25211
  for (const entry of entries) {
25212
25212
  if (!entry.isDirectory())
@@ -25235,7 +25235,7 @@ async function verifyLink(linkPath, visited) {
25235
25235
  visited.add(resolvedLink);
25236
25236
  let stats;
25237
25237
  try {
25238
- stats = await fs23.promises.lstat(linkPath);
25238
+ stats = await fs24.promises.lstat(linkPath);
25239
25239
  } catch {
25240
25240
  return "broken";
25241
25241
  }
@@ -25243,13 +25243,13 @@ async function verifyLink(linkPath, visited) {
25243
25243
  return "ok";
25244
25244
  let rawTarget;
25245
25245
  try {
25246
- rawTarget = await fs23.promises.readlink(linkPath);
25246
+ rawTarget = await fs24.promises.readlink(linkPath);
25247
25247
  } catch {
25248
25248
  return "broken";
25249
25249
  }
25250
25250
  const target = path29.isAbsolute(rawTarget) ? rawTarget : path29.resolve(path29.dirname(linkPath), rawTarget);
25251
25251
  try {
25252
- const targetStats = await fs23.promises.lstat(target);
25252
+ const targetStats = await fs24.promises.lstat(target);
25253
25253
  if (!targetStats.isSymbolicLink())
25254
25254
  return "ok";
25255
25255
  } catch {
@@ -25260,7 +25260,7 @@ async function verifyLink(linkPath, visited) {
25260
25260
  async function verify(workspacePath) {
25261
25261
  const result = { ok: true, broken: [], cycles: [] };
25262
25262
  try {
25263
- const entries = await fs23.promises.readdir(workspacePath, { withFileTypes: true });
25263
+ const entries = await fs24.promises.readdir(workspacePath, { withFileTypes: true });
25264
25264
  for (const entry of entries) {
25265
25265
  if (entry.name === MANIFEST_FILE || entry.name === AGENTS_FILE || !entry.isSymbolicLink())
25266
25266
  continue;
@@ -27195,7 +27195,7 @@ var init_DetailsTab = __esm(() => {
27195
27195
  });
27196
27196
 
27197
27197
  // src/lib/changes.ts
27198
- import fs33 from "node:fs";
27198
+ import fs34 from "node:fs";
27199
27199
  import path37 from "node:path";
27200
27200
  function cacheKey(opts, filePath) {
27201
27201
  return [opts.repoPath, opts.branch, opts.scope, filePath ?? ""].join("\x00");
@@ -27328,7 +27328,7 @@ async function getChangedFiles(opts) {
27328
27328
  continue;
27329
27329
  try {
27330
27330
  const full = path37.join(opts.repoPath, p);
27331
- const buf = fs33.readFileSync(full);
27331
+ const buf = fs34.readFileSync(full);
27332
27332
  const binary = buf.includes(0);
27333
27333
  const str = binary ? "" : buf.toString("utf8");
27334
27334
  const added = binary ? 0 : str.split(`
@@ -27379,7 +27379,7 @@ async function getFileDiff(opts) {
27379
27379
  if (!rawDiff && opts.scope === "worktree" && changedFile.status === "A") {
27380
27380
  try {
27381
27381
  const full = path37.join(opts.repoPath, opts.filePath);
27382
- const buf = fs33.readFileSync(full);
27382
+ const buf = fs34.readFileSync(full);
27383
27383
  if (!buf.includes(0)) {
27384
27384
  const content = buf.toString("utf8");
27385
27385
  const lines = content.split(`
@@ -28096,10 +28096,11 @@ var init_Overlay = __esm(() => {
28096
28096
 
28097
28097
  // src/tui/components/HelpOverlay.tsx
28098
28098
  import { jsx as jsx10, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
28099
- function HelpOverlay() {
28099
+ function HelpOverlay({ withoutDetails = false }) {
28100
28100
  const theme = useTheme();
28101
+ const groups = withoutDetails ? HELP_GROUPS.filter((g) => g.title !== "Terminal & Tabs" && !g.title.startsWith("Changes Explorer")) : HELP_GROUPS;
28101
28102
  return /* @__PURE__ */ jsxs8(Overlay, {
28102
- title: "Help",
28103
+ title: withoutDetails ? "Help (repositories only)" : "Help",
28103
28104
  borderColor: theme.border,
28104
28105
  width: 100,
28105
28106
  children: [
@@ -28108,7 +28109,7 @@ function HelpOverlay() {
28108
28109
  style: { maxHeight: 38 },
28109
28110
  children: /* @__PURE__ */ jsx10("scrollbox", {
28110
28111
  flexGrow: 1,
28111
- children: HELP_GROUPS.map((group) => /* @__PURE__ */ jsxs8("box", {
28112
+ children: groups.map((group) => /* @__PURE__ */ jsxs8("box", {
28112
28113
  flexDirection: "column",
28113
28114
  style: { marginBottom: 1 },
28114
28115
  children: [
@@ -28408,7 +28409,7 @@ var init_ActionLogModal = __esm(() => {
28408
28409
  });
28409
28410
 
28410
28411
  // src/lib/history.ts
28411
- import fs34 from "fs";
28412
+ import fs35 from "fs";
28412
28413
  import path38 from "path";
28413
28414
  import os4 from "os";
28414
28415
  function getHistoryDir() {
@@ -28422,24 +28423,24 @@ function rotateHistory(maxBytes = HISTORY_MAX_BYTES, keepLines = HISTORY_ROTATE_
28422
28423
  const historyPath = getHistoryPath();
28423
28424
  let size = 0;
28424
28425
  try {
28425
- size = fs34.statSync(historyPath).size;
28426
+ size = fs35.statSync(historyPath).size;
28426
28427
  } catch {
28427
28428
  return;
28428
28429
  }
28429
28430
  if (size <= maxBytes)
28430
28431
  return;
28431
- const lines = fs34.readFileSync(historyPath, "utf-8").split(`
28432
+ const lines = fs35.readFileSync(historyPath, "utf-8").split(`
28432
28433
  `).filter(Boolean);
28433
28434
  const kept = lines.slice(-keepLines);
28434
28435
  const tmpPath = `${historyPath}.tmp.${process.pid}`;
28435
28436
  try {
28436
- fs34.writeFileSync(tmpPath, kept.length > 0 ? `${kept.join(`
28437
+ fs35.writeFileSync(tmpPath, kept.length > 0 ? `${kept.join(`
28437
28438
  `)}
28438
28439
  ` : "", "utf-8");
28439
- fs34.renameSync(tmpPath, historyPath);
28440
+ fs35.renameSync(tmpPath, historyPath);
28440
28441
  } catch (err) {
28441
- if (fs34.existsSync(tmpPath)) {
28442
- fs34.unlinkSync(tmpPath);
28442
+ if (fs35.existsSync(tmpPath)) {
28443
+ fs35.unlinkSync(tmpPath);
28443
28444
  }
28444
28445
  throw err;
28445
28446
  }
@@ -28447,18 +28448,18 @@ function rotateHistory(maxBytes = HISTORY_MAX_BYTES, keepLines = HISTORY_ROTATE_
28447
28448
  function appendHistory(entry) {
28448
28449
  try {
28449
28450
  const dir = getHistoryDir();
28450
- if (!fs34.existsSync(dir)) {
28451
- fs34.mkdirSync(dir, { recursive: true });
28451
+ if (!fs35.existsSync(dir)) {
28452
+ fs35.mkdirSync(dir, { recursive: true });
28452
28453
  }
28453
28454
  rotateHistory();
28454
- fs34.appendFileSync(getHistoryPath(), `${JSON.stringify(entry)}
28455
+ fs35.appendFileSync(getHistoryPath(), `${JSON.stringify(entry)}
28455
28456
  `, "utf-8");
28456
28457
  } catch {}
28457
28458
  }
28458
28459
  function readRecentHistory(limit = 50) {
28459
28460
  let content;
28460
28461
  try {
28461
- content = fs34.readFileSync(getHistoryPath(), "utf-8");
28462
+ content = fs35.readFileSync(getHistoryPath(), "utf-8");
28462
28463
  } catch {
28463
28464
  return [];
28464
28465
  }
@@ -29811,7 +29812,7 @@ function createTerminalResizeScheduler(getSessionsByKey, resizeSession, onSettle
29811
29812
  flush
29812
29813
  };
29813
29814
  }
29814
- function App({ opts }) {
29815
+ function App({ opts, withoutDetails = false }) {
29815
29816
  const renderer = useRenderer2();
29816
29817
  const { blocks, loading, refreshing, error: error52, warnings, lastRefreshed, pendingRepos, favorites, refresh, clearWarnings, applyFavorites } = useWorktrees(opts);
29817
29818
  const [selectedIndex, setSelectedIndex] = useState11(0);
@@ -30097,6 +30098,10 @@ function App({ opts }) {
30097
30098
  setChangesFocused(id === "changes" && selectedRow != null && !selectedRow.isMainCheckout && selectedRow.dirtyFiles.length > 0);
30098
30099
  }, [activeTabId, renderer, worktreeKey, sessionsForSelected, selectedRow]);
30099
30100
  const handleAddSession = useCallback7(() => {
30101
+ if (withoutDetails) {
30102
+ flash("Terminal sessions disabled (--wo-details)");
30103
+ return;
30104
+ }
30100
30105
  if (!selectedRow) {
30101
30106
  flash("No worktree selected");
30102
30107
  return;
@@ -30121,7 +30126,7 @@ function App({ opts }) {
30121
30126
  setTerminalFocused(true);
30122
30127
  setChangesFocused(false);
30123
30128
  flash(`Session ${session.label} created`, 2000);
30124
- }, [selectedRow, terminalSessions, flash, totalWidth, splitRatio, totalHeight, activeTabId, sessionsForSelected]);
30129
+ }, [selectedRow, terminalSessions, flash, totalWidth, splitRatio, totalHeight, activeTabId, sessionsForSelected, withoutDetails]);
30125
30130
  const handleCloseSession = useCallback7((id) => {
30126
30131
  let targetRepo;
30127
30132
  let targetBranch;
@@ -30498,6 +30503,10 @@ function App({ opts }) {
30498
30503
  return;
30499
30504
  }
30500
30505
  if (key === "t") {
30506
+ if (withoutDetails) {
30507
+ flash("Terminal sessions disabled (--wo-details)");
30508
+ return;
30509
+ }
30501
30510
  if (!selectedRow)
30502
30511
  return;
30503
30512
  const sessions = terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path);
@@ -30508,7 +30517,7 @@ function App({ opts }) {
30508
30517
  handleAddSession();
30509
30518
  return;
30510
30519
  }
30511
- }, [selectedRow, selection, combinedWarnings, busyRepos, busyRowPaths, doRefresh, flash, blocks, terminalSessions]);
30520
+ }, [selectedRow, selection, combinedWarnings, busyRepos, busyRowPaths, doRefresh, flash, blocks, terminalSessions, withoutDetails]);
30512
30521
  useKeyboard4((key) => {
30513
30522
  if (changesFocused) {
30514
30523
  if (key.ctrl && key.name === "g" || key.name === "escape") {
@@ -30566,6 +30575,8 @@ function App({ opts }) {
30566
30575
  return;
30567
30576
  }
30568
30577
  if (key.ctrl && key.name === "g") {
30578
+ if (withoutDetails)
30579
+ return;
30569
30580
  if (sessionsForSelected.length > 0) {
30570
30581
  const targetId = activeTabId !== "details" ? activeTabId : sessionsForSelected[0].id;
30571
30582
  if (worktreeKey) {
@@ -30923,6 +30934,10 @@ function App({ opts }) {
30923
30934
  return;
30924
30935
  }
30925
30936
  if (key.name === "t") {
30937
+ if (withoutDetails) {
30938
+ flash("Terminal sessions disabled (--wo-details)");
30939
+ return;
30940
+ }
30926
30941
  if (!selectedRow) {
30927
30942
  flash("No worktree selected");
30928
30943
  return;
@@ -30937,7 +30952,7 @@ function App({ opts }) {
30937
30952
  }
30938
30953
  });
30939
30954
  const rawLeft = Math.floor(totalWidth * splitRatio);
30940
- const leftCols = Math.max(20, Math.min(totalWidth - 20 - DIVIDER_WIDTH, rawLeft));
30955
+ const leftCols = withoutDetails ? totalWidth : Math.max(20, Math.min(totalWidth - 20 - DIVIDER_WIDTH, rawLeft));
30941
30956
  const rightCols = Math.max(20, totalWidth - leftCols - DIVIDER_WIDTH);
30942
30957
  const paneCols = Math.max(20, rightCols - 4);
30943
30958
  const paneRows = Math.max(10, totalHeight - 10);
@@ -31000,13 +31015,13 @@ function App({ opts }) {
31000
31015
  onToggleSelect: (path39) => setSelection((prev) => toggleSelection(prev, path39))
31001
31016
  })
31002
31017
  }),
31003
- /* @__PURE__ */ jsx18(Divider, {
31018
+ !withoutDetails && /* @__PURE__ */ jsx18(Divider, {
31004
31019
  splitRatio,
31005
31020
  totalWidth,
31006
31021
  onChange: setSplitRatio,
31007
31022
  onDraggingChange: setIsResizing
31008
31023
  }),
31009
- /* @__PURE__ */ jsx18("box", {
31024
+ !withoutDetails && /* @__PURE__ */ jsx18("box", {
31010
31025
  width: rightCols,
31011
31026
  height: "100%",
31012
31027
  flexDirection: "column",
@@ -31083,7 +31098,9 @@ function App({ opts }) {
31083
31098
  },
31084
31099
  onCancel: () => setWorkspacePickerOpen(false)
31085
31100
  }),
31086
- modal.type === "help" && /* @__PURE__ */ jsx18(HelpOverlay, {}),
31101
+ modal.type === "help" && /* @__PURE__ */ jsx18(HelpOverlay, {
31102
+ withoutDetails
31103
+ }),
31087
31104
  modal.type === "history" && /* @__PURE__ */ jsx18(HistoryOverlay, {}),
31088
31105
  modal.type === "warnings" && /* @__PURE__ */ jsx18(WarningsOverlay, {
31089
31106
  warnings: combinedWarnings,
@@ -31377,7 +31394,7 @@ __export(exports_tui, {
31377
31394
  import { createCliRenderer, TextTableRenderable, EmbeddedTerminalRenderable } from "@opentui/core";
31378
31395
  import { createRoot, extend as extend2 } from "@opentui/react";
31379
31396
  import { jsx as jsx19 } from "@opentui/react/jsx-runtime";
31380
- async function runTerminal(opts) {
31397
+ async function runTerminal(opts, runOpts = {}) {
31381
31398
  setQuiet(true);
31382
31399
  const prevLog = console.log;
31383
31400
  const prevInfo = console.info;
@@ -31452,7 +31469,8 @@ async function runTerminal(opts) {
31452
31469
  const root = createRoot(renderer);
31453
31470
  try {
31454
31471
  root.render(/* @__PURE__ */ jsx19(App, {
31455
- opts
31472
+ opts,
31473
+ withoutDetails: runOpts.withoutDetails ?? false
31456
31474
  }));
31457
31475
  } catch (err) {
31458
31476
  restoreConsole();
@@ -34137,8 +34155,66 @@ init_config();
34137
34155
  init_log();
34138
34156
  init_git();
34139
34157
  init_resolver();
34140
- import fs22 from "fs";
34158
+ import fs23 from "fs";
34141
34159
  import path28 from "path";
34160
+
34161
+ // src/lib/worktree-remove.ts
34162
+ init_git();
34163
+ import fs22 from "fs";
34164
+ function isSubmoduleWorktreeError(message) {
34165
+ return message.includes("submodule") || message.includes("containing submodules");
34166
+ }
34167
+ function isMissingWorktreePathError(message) {
34168
+ return message.includes("ENOENT") || message.includes("No such file") || message.includes("does not exist") || message.includes("not a git repository") || message.includes("cannot change to");
34169
+ }
34170
+ function isRecoverableWorktreeRemoveError(message) {
34171
+ return message.includes("not a working tree") || message.includes("already removed") || isSubmoduleWorktreeError(message) || isMissingWorktreePathError(message);
34172
+ }
34173
+ function removePathIfExists(wtPath, opts) {
34174
+ if (!opts.dryRun && fs22.existsSync(wtPath)) {
34175
+ fs22.rmSync(wtPath, { recursive: true, force: true });
34176
+ }
34177
+ }
34178
+ async function pruneWorktreeAdmin(mainPath, opts) {
34179
+ try {
34180
+ await gitExec(["-C", mainPath, "worktree", "prune"], opts);
34181
+ } catch {}
34182
+ }
34183
+ async function removeWorktreeSafely(params) {
34184
+ const { mainPath, wtPath, force, opts } = params;
34185
+ const args = ["-C", mainPath, "worktree", "remove", wtPath];
34186
+ if (force) {
34187
+ args.push("--force");
34188
+ }
34189
+ try {
34190
+ await gitExec(args, opts);
34191
+ return { usedForce: force, manualCleanup: false };
34192
+ } catch (err) {
34193
+ const msg = err instanceof Error ? err.message : String(err);
34194
+ if (isSubmoduleWorktreeError(msg) && !force) {
34195
+ try {
34196
+ await gitExec(["-C", mainPath, "worktree", "remove", "--force", wtPath], opts);
34197
+ return { usedForce: true, manualCleanup: false };
34198
+ } catch (forceErr) {
34199
+ const forceMsg = forceErr instanceof Error ? forceErr.message : String(forceErr);
34200
+ if (isRecoverableWorktreeRemoveError(forceMsg)) {
34201
+ await pruneWorktreeAdmin(mainPath, opts);
34202
+ removePathIfExists(wtPath, opts);
34203
+ return { usedForce: true, manualCleanup: true };
34204
+ }
34205
+ throw forceErr;
34206
+ }
34207
+ }
34208
+ if (isRecoverableWorktreeRemoveError(msg)) {
34209
+ await pruneWorktreeAdmin(mainPath, opts);
34210
+ removePathIfExists(wtPath, opts);
34211
+ return { usedForce: force, manualCleanup: true };
34212
+ }
34213
+ throw err;
34214
+ }
34215
+ }
34216
+
34217
+ // src/commands/pull.ts
34142
34218
  init_forge();
34143
34219
  init_stack();
34144
34220
  function registerPullCommand(program2) {
@@ -34255,7 +34331,7 @@ function registerPullCommand(program2) {
34255
34331
  const worktrees = await getWorktreeList(target.mainPath);
34256
34332
  const existingWorktree = worktrees.find((w) => w.path === wtPath || w.branch === branch);
34257
34333
  const localBranchFound = await localBranchExists(target.mainPath, branch, globalOpts);
34258
- const dirExists = Boolean(existingWorktree) || fs22.existsSync(wtPath);
34334
+ const dirExists = Boolean(existingWorktree) || fs23.existsSync(wtPath);
34259
34335
  if (localBranchFound || dirExists) {
34260
34336
  if (options.force) {
34261
34337
  stepWarning(`Branch '${branch}' already exists — --force: will override`, dirExists ? wtPath : "local branch exists");
@@ -34268,17 +34344,22 @@ function registerPullCommand(program2) {
34268
34344
  if (options.force && (localBranchFound || dirExists)) {
34269
34345
  if (existingWorktree) {
34270
34346
  try {
34271
- await gitExec(["-C", target.mainPath, "worktree", "remove", "--force", existingWorktree.path], { verbose: globalOpts.verbose, dryRun: globalOpts.dryRun });
34347
+ await removeWorktreeSafely({
34348
+ mainPath: target.mainPath,
34349
+ wtPath: existingWorktree.path,
34350
+ force: true,
34351
+ opts: { verbose: globalOpts.verbose, dryRun: globalOpts.dryRun }
34352
+ });
34272
34353
  } catch (err) {
34273
34354
  const message = err instanceof Error ? err.message : String(err);
34274
- if (!message.includes("not a working tree")) {
34355
+ if (!isRecoverableWorktreeRemoveError(message)) {
34275
34356
  stepWarning("Worktree removal failed", message.split(`
34276
34357
  `)[0] ?? message);
34277
34358
  }
34278
34359
  }
34279
34360
  }
34280
- if (!globalOpts.dryRun && fs22.existsSync(wtPath)) {
34281
- fs22.rmSync(wtPath, { recursive: true, force: true });
34361
+ if (!globalOpts.dryRun && fs23.existsSync(wtPath)) {
34362
+ fs23.rmSync(wtPath, { recursive: true, force: true });
34282
34363
  }
34283
34364
  if (localBranchFound) {
34284
34365
  try {
@@ -34322,7 +34403,7 @@ function registerPullCommand(program2) {
34322
34403
  }
34323
34404
  stepSuccess("Fetched");
34324
34405
  if (!globalOpts.dryRun) {
34325
- fs22.mkdirSync(path28.dirname(wtPath), { recursive: true });
34406
+ fs23.mkdirSync(path28.dirname(wtPath), { recursive: true });
34326
34407
  }
34327
34408
  try {
34328
34409
  const createBranchFlag = options.force ? "-B" : "-b";
@@ -34456,10 +34537,10 @@ function registerPullBranchCommand(program2) {
34456
34537
  init_config();
34457
34538
  init_log();
34458
34539
  init_git();
34540
+ import fs25 from "fs";
34541
+ import path30 from "path";
34459
34542
  init_resolver();
34460
34543
  init_path_safety();
34461
- import fs24 from "fs";
34462
- import path30 from "path";
34463
34544
 
34464
34545
  // src/lib/prompts.ts
34465
34546
  import * as readline2 from "readline";
@@ -34507,15 +34588,9 @@ init_workspace();
34507
34588
  function errorMessage2(err) {
34508
34589
  return err instanceof Error ? err.message : String(err);
34509
34590
  }
34510
- function isMissingWorktreePathError(message) {
34511
- return message.includes("ENOENT") || message.includes("No such file") || message.includes("does not exist") || message.includes("not a git repository") || message.includes("cannot change to");
34512
- }
34513
- function isRecoverableWorktreeRemoveError(message) {
34514
- return message.includes("not a working tree") || message.includes("already removed") || message.includes("submodule") || message.includes("containing submodules") || isMissingWorktreePathError(message);
34515
- }
34516
34591
  function removeExistingPath(wtPath, opts) {
34517
- if (!opts.dryRun && fs24.existsSync(wtPath)) {
34518
- fs24.rmSync(wtPath, { recursive: true, force: true });
34592
+ if (!opts.dryRun && fs25.existsSync(wtPath)) {
34593
+ fs25.rmSync(wtPath, { recursive: true, force: true });
34519
34594
  }
34520
34595
  }
34521
34596
  async function removeLocalBranchIfExists(repoPath, branch, opts) {
@@ -34604,7 +34679,7 @@ function registerRemoveCommand(program2) {
34604
34679
  const worktrees = await getWorktreeList(repo.mainPath);
34605
34680
  const target = findWorktreeForBranch(worktrees, branch, repo.mainPath, candidatePath);
34606
34681
  if (!target) {
34607
- if (options.force && (fs24.existsSync(candidatePath) || await localBranchExists(repo.mainPath, branch, globalOpts))) {
34682
+ if (options.force && (fs25.existsSync(candidatePath) || await localBranchExists(repo.mainPath, branch, globalOpts))) {
34608
34683
  if (!await confirmDeletionIfNeeded(repo, candidatePath, options, globalOpts)) {
34609
34684
  skipCount++;
34610
34685
  continue;
@@ -34646,7 +34721,7 @@ function registerRemoveCommand(program2) {
34646
34721
  }
34647
34722
  } catch (err) {
34648
34723
  const msg = errorMessage2(err);
34649
- if (isMissingWorktreePathError(msg) || !fs24.existsSync(wtPath)) {
34724
+ if (isMissingWorktreePathError(msg) || !fs25.existsSync(wtPath)) {
34650
34725
  stepWarning("Worktree path missing", "continuing with removal cleanup");
34651
34726
  } else {
34652
34727
  stepError("Failed to check for uncommitted changes", msg);
@@ -34655,7 +34730,7 @@ function registerRemoveCommand(program2) {
34655
34730
  }
34656
34731
  }
34657
34732
  }
34658
- if (options.force && !globalOpts.dryRun && !fs24.existsSync(wtPath)) {
34733
+ if (options.force && !globalOpts.dryRun && !fs25.existsSync(wtPath)) {
34659
34734
  if (!await confirmDeletionIfNeeded(repo, wtPath, options, globalOpts)) {
34660
34735
  skipCount++;
34661
34736
  continue;
@@ -34679,52 +34754,16 @@ function registerRemoveCommand(program2) {
34679
34754
  skipCount++;
34680
34755
  continue;
34681
34756
  }
34682
- const args = ["-C", repo.mainPath, "worktree", "remove", wtPath];
34683
- if (options.force) {
34684
- args.push("--force");
34685
- }
34686
- try {
34687
- await gitExec(args, globalOpts);
34688
- stepSuccess("Worktree removed", wtPath);
34689
- } catch (err) {
34690
- const msg = errorMessage2(err);
34691
- const isSubmodule = msg.includes("submodule") || msg.includes("containing submodules");
34692
- if (isSubmodule && !options.force && !args.includes("--force")) {
34693
- stepWarning("Worktree contains submodules — retrying with --force", msg.split(`
34694
- `)[0] ?? msg);
34695
- try {
34696
- await gitExec(["-C", repo.mainPath, "worktree", "remove", "--force", wtPath], globalOpts);
34697
- stepSuccess("Worktree removed", wtPath);
34698
- } catch (forceErr) {
34699
- const forceMsg = errorMessage2(forceErr);
34700
- if (isRecoverableWorktreeRemoveError(forceMsg)) {
34701
- stepWarning("Worktree already removed or invalid", forceMsg);
34702
- try {
34703
- await gitExec(["-C", repo.mainPath, "worktree", "prune"], globalOpts);
34704
- } catch (pruneErr) {
34705
- stepWarning("Worktree prune failed", errorMessage2(pruneErr));
34706
- }
34707
- removeExistingPath(wtPath, globalOpts);
34708
- await removeLocalBranchBestEffort(repo.mainPath, branch, globalOpts);
34709
- stepSuccess("Worktree removed", wtPath);
34710
- } else {
34711
- throw forceErr;
34712
- }
34713
- }
34714
- } else if (isRecoverableWorktreeRemoveError(msg)) {
34715
- stepWarning("Worktree already removed or invalid", msg);
34716
- try {
34717
- await gitExec(["-C", repo.mainPath, "worktree", "prune"], globalOpts);
34718
- } catch (pruneErr) {
34719
- stepWarning("Worktree prune failed", errorMessage2(pruneErr));
34720
- }
34721
- removeExistingPath(wtPath, globalOpts);
34722
- await removeLocalBranchBestEffort(repo.mainPath, branch, globalOpts);
34723
- stepSuccess("Worktree removed", wtPath);
34724
- } else {
34725
- throw err;
34726
- }
34757
+ const result = await removeWorktreeSafely({
34758
+ mainPath: repo.mainPath,
34759
+ wtPath,
34760
+ force: !!options.force,
34761
+ opts: globalOpts
34762
+ });
34763
+ if (result.manualCleanup) {
34764
+ await removeLocalBranchBestEffort(repo.mainPath, branch, globalOpts);
34727
34765
  }
34766
+ stepSuccess("Worktree removed", wtPath);
34728
34767
  await finishCleanup(repo, wtPath, branch, globalOpts);
34729
34768
  await unlinkRemovedWorktreeFromWorkspaces({ workspaceRoot, repo: repo.name, branch });
34730
34769
  if (children.length > 0) {
@@ -34751,9 +34790,9 @@ init_source();
34751
34790
  init_config();
34752
34791
  init_log();
34753
34792
  init_git();
34793
+ import path31 from "path";
34754
34794
  init_resolver();
34755
34795
  init_forge();
34756
- import path31 from "path";
34757
34796
 
34758
34797
  // src/lib/prune.ts
34759
34798
  function selectMergedCandidates(worktrees, mainPath, prMap) {
@@ -34890,16 +34929,17 @@ function registerPruneCommand(program2) {
34890
34929
  repoHeader(repo.name);
34891
34930
  currentRepo = repo.name;
34892
34931
  }
34893
- const args = ["-C", repo.mainPath, "worktree", "remove", candidate.path];
34894
- if (options.force) {
34895
- args.push("--force");
34896
- }
34897
34932
  try {
34898
- await gitExec(args, globalOpts);
34933
+ await removeWorktreeSafely({
34934
+ mainPath: repo.mainPath,
34935
+ wtPath: candidate.path,
34936
+ force: !!options.force,
34937
+ opts: globalOpts
34938
+ });
34899
34939
  stepSuccess("Worktree removed", label);
34900
34940
  } catch (err) {
34901
34941
  const msg = err instanceof Error ? err.message : String(err);
34902
- if (msg.includes("not a working tree") || msg.includes("already removed")) {
34942
+ if (isRecoverableWorktreeRemoveError(msg)) {
34903
34943
  stepWarning("Worktree already removed or invalid", msg);
34904
34944
  skippedCount++;
34905
34945
  continue;
@@ -35002,7 +35042,7 @@ init_source();
35002
35042
  init_stack();
35003
35043
  init_stack();
35004
35044
  import path32 from "path";
35005
- import fs25 from "fs";
35045
+ import fs26 from "fs";
35006
35046
  function buildLsJson(reposData) {
35007
35047
  const result = [];
35008
35048
  for (const repo of reposData) {
@@ -35112,7 +35152,7 @@ function registerLsCommand(program2) {
35112
35152
  statusStr = source_default.blue("[main checkout]");
35113
35153
  } else if (wt.isLocked) {
35114
35154
  statusStr = source_default.red("locked \uD83D\uDD12");
35115
- } else if (fs25.existsSync(wt.path)) {
35155
+ } else if (fs26.existsSync(wt.path)) {
35116
35156
  try {
35117
35157
  dirtyFiles = await getDirtyFiles(wt.path);
35118
35158
  if (dirtyFiles.length > 0) {
@@ -35238,7 +35278,7 @@ end
35238
35278
  init_config();
35239
35279
  init_log();
35240
35280
  init_git();
35241
- import fs26 from "fs";
35281
+ import fs27 from "fs";
35242
35282
  init_resolver();
35243
35283
  init_stack();
35244
35284
  function registerRebaseCommand(program2) {
@@ -35253,7 +35293,7 @@ function registerRebaseCommand(program2) {
35253
35293
  repoHeader(repo.name);
35254
35294
  const mainBranch = await resolveMainBranch(repo, config2);
35255
35295
  const wtPath = getWorktreePath(repo, branch);
35256
- if (!fs26.existsSync(wtPath)) {
35296
+ if (!fs27.existsSync(wtPath)) {
35257
35297
  stepError("No worktree found", `${wtPath} (skipped)`);
35258
35298
  failCount++;
35259
35299
  continue;
@@ -35379,7 +35419,7 @@ init_execa();
35379
35419
  init_config();
35380
35420
  init_log();
35381
35421
  init_resolver();
35382
- import fs27 from "fs";
35422
+ import fs28 from "fs";
35383
35423
  import path33 from "path";
35384
35424
  function registerSyncCommand(program2) {
35385
35425
  program2.command("sync <branch>").description("re-copy sync files + run post_sync").option("--repo <repos...>", "comma-separated list of repos to target").action(async (branch, _options, cmd) => {
@@ -35391,14 +35431,14 @@ function registerSyncCommand(program2) {
35391
35431
  for (const repo of repos) {
35392
35432
  repoHeader(repo.name);
35393
35433
  const wtPath = getWorktreePath(repo, branch);
35394
- if (!fs27.existsSync(wtPath)) {
35434
+ if (!fs28.existsSync(wtPath)) {
35395
35435
  stepWarning("No worktree found", `${wtPath} (skipped)`);
35396
35436
  continue;
35397
35437
  }
35398
35438
  try {
35399
35439
  if (repo.config.sync_files) {
35400
35440
  for (const file2 of repo.config.sync_files) {
35401
- if (fs27.existsSync(path33.join(repo.mainPath, file2))) {
35441
+ if (fs28.existsSync(path33.join(repo.mainPath, file2))) {
35402
35442
  if (!opts.dryRun) {
35403
35443
  syncEntry(repo.mainPath, wtPath, file2);
35404
35444
  }
@@ -35451,14 +35491,14 @@ function registerSyncCommand(program2) {
35451
35491
  }
35452
35492
  }
35453
35493
  const wtNodeModules = path33.join(wtPath, "node_modules");
35454
- if (fs27.existsSync(wtNodeModules) && fs27.lstatSync(wtNodeModules).isSymbolicLink()) {
35494
+ if (fs28.existsSync(wtNodeModules) && fs28.lstatSync(wtNodeModules).isSymbolicLink()) {
35455
35495
  const lockfiles = ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lockb", "bun.lock"];
35456
35496
  for (const lock of lockfiles) {
35457
35497
  const mainLock = path33.join(repo.mainPath, lock);
35458
35498
  const wtLock = path33.join(wtPath, lock);
35459
- if (fs27.existsSync(mainLock) && fs27.existsSync(wtLock)) {
35460
- const mainContent = fs27.readFileSync(mainLock);
35461
- const wtContent = fs27.readFileSync(wtLock);
35499
+ if (fs28.existsSync(mainLock) && fs28.existsSync(wtLock)) {
35500
+ const mainContent = fs28.readFileSync(mainLock);
35501
+ const wtContent = fs28.readFileSync(wtLock);
35462
35502
  if (!mainContent.equals(wtContent)) {
35463
35503
  stepWarning(`${lock} differs from main — node_modules is symlinked`);
35464
35504
  indented(`Run: wtx deps ${branch} --repo ${repo.name} --install`);
@@ -35487,7 +35527,7 @@ init_config();
35487
35527
  init_log();
35488
35528
  init_resolver();
35489
35529
  init_deps();
35490
- import fs28 from "fs";
35530
+ import fs29 from "fs";
35491
35531
  function registerDepsCommand(program2) {
35492
35532
  program2.command("deps [branch]").description("Manage node_modules strategy per worktree").option("-r, --repo <repos...>", "Target specific repo(s)").option("--install", "Switch to independent node_modules (run install)").option("--symlink", "Switch to legacy symlinked node_modules").option("--json", "Output machine-readable JSON state").action(async (branch, options) => {
35493
35533
  const globalOpts = program2.opts();
@@ -35524,7 +35564,7 @@ function registerDepsCommand(program2) {
35524
35564
  continue;
35525
35565
  }
35526
35566
  const wtPath = getWorktreePath(repo, branch);
35527
- if (!fs28.existsSync(wtPath)) {
35567
+ if (!fs29.existsSync(wtPath)) {
35528
35568
  if (options.json) {
35529
35569
  jsonResults[repo.name] = { error: "No worktree found", branch };
35530
35570
  } else {
@@ -35582,7 +35622,7 @@ function registerDepsCommand(program2) {
35582
35622
  init_config();
35583
35623
  init_log();
35584
35624
  init_resolver();
35585
- import fs29 from "fs";
35625
+ import fs30 from "fs";
35586
35626
  init_git();
35587
35627
  async function resolveMainCheckoutPath(repoCtx, branch) {
35588
35628
  try {
@@ -35607,7 +35647,7 @@ function registerOpenCommand(program2) {
35607
35647
  for (const repo of repos) {
35608
35648
  const wtPath = getWorktreePath(repo, branch);
35609
35649
  let targetPath = wtPath;
35610
- if (!fs29.existsSync(wtPath)) {
35650
+ if (!fs30.existsSync(wtPath)) {
35611
35651
  const mainCheckoutPath = await resolveMainCheckoutPath(repo, branch);
35612
35652
  if (!mainCheckoutPath) {
35613
35653
  continue;
@@ -35643,7 +35683,7 @@ import path35 from "path";
35643
35683
  init_git();
35644
35684
  init_resolver();
35645
35685
  init_path_safety();
35646
- import fs30 from "fs";
35686
+ import fs31 from "fs";
35647
35687
  import path34 from "path";
35648
35688
  init_workspace();
35649
35689
  async function getUpstream(wtPath) {
@@ -35686,7 +35726,7 @@ async function planRename(repo, oldBranch, newBranch, opts) {
35686
35726
  throw new Error(`Worktree '${oldBranch}' is locked — unlock it before renaming`);
35687
35727
  }
35688
35728
  const newPath = `${repo.wtRoot}/${newBranch}`;
35689
- if (fs30.existsSync(newPath)) {
35729
+ if (fs31.existsSync(newPath)) {
35690
35730
  throw new Error(`Target path already exists: ${newPath}`);
35691
35731
  }
35692
35732
  if (!opts.dryRun && await localBranchExists(repo.mainPath, newBranch, opts)) {
@@ -35719,7 +35759,7 @@ async function renameWorktree(params) {
35719
35759
  }
35720
35760
  await gitExec(["-C", planned.worktreePath, "branch", "-m", oldBranch, newBranch], opts);
35721
35761
  try {
35722
- fs30.mkdirSync(path34.dirname(planned.newPath), { recursive: true });
35762
+ fs31.mkdirSync(path34.dirname(planned.newPath), { recursive: true });
35723
35763
  await gitExec(["-C", repo.mainPath, "worktree", "move", planned.worktreePath, planned.newPath], opts);
35724
35764
  } catch (err) {
35725
35765
  const message = err instanceof Error ? err.message : String(err);
@@ -35738,10 +35778,10 @@ async function renameWorktree(params) {
35738
35778
  const resyncedFiles = [];
35739
35779
  const keptLocalSyncFiles = [];
35740
35780
  for (const entry of repo.config.sync_files ?? []) {
35741
- if (!fs30.existsSync(path34.join(repo.mainPath, entry)))
35781
+ if (!fs31.existsSync(path34.join(repo.mainPath, entry)))
35742
35782
  continue;
35743
35783
  const destPath = path34.join(planned.newPath, entry);
35744
- if (!fs30.existsSync(destPath)) {
35784
+ if (!fs31.existsSync(destPath)) {
35745
35785
  if (syncEntry(repo.mainPath, planned.newPath, entry)) {
35746
35786
  resyncedFiles.push(entry);
35747
35787
  }
@@ -35880,7 +35920,7 @@ init_resolver();
35880
35920
  init_deps();
35881
35921
  init_forge();
35882
35922
  init_types2();
35883
- import fs31 from "fs";
35923
+ import fs32 from "fs";
35884
35924
  init_owner();
35885
35925
  init_stack();
35886
35926
  init_source();
@@ -35930,7 +35970,7 @@ function registerStatusCommand(program2) {
35930
35970
  const jsonOutputs = [];
35931
35971
  for (const repo of repos) {
35932
35972
  const wtPath = getWorktreePath(repo, branch);
35933
- if (!fs31.existsSync(wtPath)) {
35973
+ if (!fs32.existsSync(wtPath)) {
35934
35974
  continue;
35935
35975
  }
35936
35976
  found++;
@@ -36230,7 +36270,7 @@ function registerPrsCommand(program2) {
36230
36270
 
36231
36271
  // src/commands/skill.ts
36232
36272
  init_log();
36233
- import fs32 from "fs";
36273
+ import fs33 from "fs";
36234
36274
  import path36 from "path";
36235
36275
  import { URL as URL2 } from "url";
36236
36276
  var __dirname2 = new URL2(".", import.meta.url).pathname;
@@ -36387,7 +36427,7 @@ function registerSkillCommand(program2) {
36387
36427
  }
36388
36428
  let projectRoot = path36.resolve(__dirname2, "..", "..");
36389
36429
  let skillPath = path36.join(projectRoot, "skills", `${platform2}.md`);
36390
- if (fs32.existsSync(skillPath)) {
36430
+ if (fs33.existsSync(skillPath)) {
36391
36431
  process.stdout.write(skillPath + `
36392
36432
  `);
36393
36433
  } else {
@@ -36400,7 +36440,7 @@ function registerSkillCommand(program2) {
36400
36440
 
36401
36441
  // src/commands/terminal.ts
36402
36442
  import { spawnSync as spawnSync3 } from "node:child_process";
36403
- import fs35 from "node:fs";
36443
+ import fs36 from "node:fs";
36404
36444
  import path39 from "node:path";
36405
36445
  import { fileURLToPath as fileURLToPath4 } from "node:url";
36406
36446
  function findEntry() {
@@ -36416,20 +36456,20 @@ function findEntry() {
36416
36456
  for (const p of candidates) {
36417
36457
  try {
36418
36458
  if (p.endsWith(".mjs") || p.endsWith(".js")) {
36419
- if (fs35.existsSync(p) && fs35.statSync(p).isFile())
36459
+ if (fs36.existsSync(p) && fs36.statSync(p).isFile())
36420
36460
  return p;
36421
- } else if (fs35.existsSync(p)) {
36422
- const stat = fs35.statSync(p);
36461
+ } else if (fs36.existsSync(p)) {
36462
+ const stat = fs36.statSync(p);
36423
36463
  if (stat.isFile()) {
36424
- const head = fs35.readFileSync(p, "utf8").slice(0, 1024);
36464
+ const head = fs36.readFileSync(p, "utf8").slice(0, 1024);
36425
36465
  if (head.includes("cli.mjs") || head.includes("#!/usr/bin/env node")) {
36426
36466
  if (p.endsWith("wtx") || p.endsWith("cli.mjs")) {
36427
- const dir = path39.dirname(fs35.realpathSync(p));
36467
+ const dir = path39.dirname(fs36.realpathSync(p));
36428
36468
  const cli = path39.join(dir, "cli.mjs");
36429
- if (fs35.existsSync(cli))
36469
+ if (fs36.existsSync(cli))
36430
36470
  return cli;
36431
36471
  const cli2 = path39.resolve(dir, "../dist/cli.mjs");
36432
- if (fs35.existsSync(cli2))
36472
+ if (fs36.existsSync(cli2))
36433
36473
  return cli2;
36434
36474
  if (head.includes("import") || head.includes("commander"))
36435
36475
  return p;
@@ -36443,7 +36483,7 @@ function findEntry() {
36443
36483
  const thisPath = fileURLToPath4(import.meta.url);
36444
36484
  const pkgRoot = path39.resolve(path39.dirname(thisPath), "../..");
36445
36485
  const cli = path39.join(pkgRoot, "dist", "cli.mjs");
36446
- if (fs35.existsSync(cli))
36486
+ if (fs36.existsSync(cli))
36447
36487
  return cli;
36448
36488
  } catch {}
36449
36489
  return null;
@@ -36485,7 +36525,7 @@ function tryRelaunchViaBun(forwardedArgs) {
36485
36525
  return null;
36486
36526
  }
36487
36527
  function registerTerminalCommand(program2) {
36488
- program2.command("terminal").description("Interactive worktree dashboard (requires Bun)").action(async () => {
36528
+ program2.command("terminal").description("Interactive worktree dashboard (requires Bun)").option("--wo-details", "Repositories panel only (hide details/right pane)").action(async (options) => {
36489
36529
  const opts = program2.optsWithGlobals();
36490
36530
  if (typeof Bun === "undefined") {
36491
36531
  const forwarded = process.argv.slice(2);
@@ -36534,7 +36574,7 @@ function registerTerminalCommand(program2) {
36534
36574
  process.env.PAGE_LIST_LOG ??= "off";
36535
36575
  process.env.OPENTUI_LOG ??= "off";
36536
36576
  const { runTerminal: runTerminal2 } = await Promise.resolve().then(() => (init_tui(), exports_tui));
36537
- await runTerminal2(opts);
36577
+ await runTerminal2(opts, { withoutDetails: Boolean(options.woDetails) });
36538
36578
  });
36539
36579
  }
36540
36580
 
@@ -36547,7 +36587,7 @@ init_deps();
36547
36587
  import readline3 from "readline";
36548
36588
  init_path_safety();
36549
36589
  init_stack();
36550
- import fs36 from "fs";
36590
+ import fs37 from "fs";
36551
36591
  async function runMcpServer(opts = {}) {
36552
36592
  const input = opts.input ?? process.stdin;
36553
36593
  const output = opts.output ?? process.stdout;
@@ -36730,7 +36770,7 @@ async function handleToolCall(name, args, config2, _opts) {
36730
36770
  for (const wt of wts) {
36731
36771
  if (!wt.path.startsWith(repo.wtRoot))
36732
36772
  continue;
36733
- const dirtyCount = fs36.existsSync(wt.path) ? (await getDirtyFiles(wt.path)).length : 0;
36773
+ const dirtyCount = fs37.existsSync(wt.path) ? (await getDirtyFiles(wt.path)).length : 0;
36734
36774
  items.push({
36735
36775
  repo: repo.name,
36736
36776
  branch: wt.branch || null,
@@ -36758,7 +36798,7 @@ async function handleToolCall(name, args, config2, _opts) {
36758
36798
  }
36759
36799
  const repo = resolveRepos(config2, [args.repo])[0];
36760
36800
  const wtPath = getWorktreePath(repo, args.branch);
36761
- if (!fs36.existsSync(wtPath)) {
36801
+ if (!fs37.existsSync(wtPath)) {
36762
36802
  throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
36763
36803
  }
36764
36804
  const mainBranch = await resolveMainBranch(repo, config2);
@@ -36877,13 +36917,18 @@ async function handleToolCall(name, args, config2, _opts) {
36877
36917
  if (children.length > 0 && args.force !== true) {
36878
36918
  throw { isToolError: true, message: `Branch has dependent worktrees: ${children.join(", ")}. Use force:true to override.` };
36879
36919
  }
36880
- if (!args.force && fs36.existsSync(wtPath)) {
36920
+ if (!args.force && fs37.existsSync(wtPath)) {
36881
36921
  const dirty = await getDirtyFiles(wtPath);
36882
36922
  if (dirty.length > 0) {
36883
36923
  throw { isToolError: true, message: `Worktree is dirty. Use force:true to remove it.` };
36884
36924
  }
36885
36925
  }
36886
- await gitExec(["-C", repo.mainPath, "worktree", "remove", args.force ? "--force" : "", wtPath].filter(Boolean));
36926
+ await removeWorktreeSafely({
36927
+ mainPath: repo.mainPath,
36928
+ wtPath,
36929
+ force: args.force === true,
36930
+ opts: { verbose: _opts.verbose === true, dryRun: false }
36931
+ });
36887
36932
  cleanupEmptyParents(repo.wtRoot, repo.mainPath, wtPath);
36888
36933
  await removeStackEntry(repo.mainPath, args.branch, { verbose: _opts.verbose === true, dryRun: false });
36889
36934
  return {
@@ -36896,7 +36941,7 @@ async function handleToolCall(name, args, config2, _opts) {
36896
36941
  }
36897
36942
  const repo = resolveRepos(config2, [args.repo])[0];
36898
36943
  const wtPath = getWorktreePath(repo, args.branch);
36899
- if (!fs36.existsSync(wtPath)) {
36944
+ if (!fs37.existsSync(wtPath)) {
36900
36945
  throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
36901
36946
  }
36902
36947
  const mainBranch = await resolveMainBranch(repo, config2);
@@ -37118,7 +37163,7 @@ init_config();
37118
37163
  init_log();
37119
37164
  init_git();
37120
37165
  init_resolver();
37121
- import fs37 from "fs";
37166
+ import fs38 from "fs";
37122
37167
  import path40 from "path";
37123
37168
  init_workspace();
37124
37169
  var DEPS_STRATEGIES2 = ["auto", "link", "symlink", "install", "off"];
@@ -37141,7 +37186,7 @@ async function resolveWorktreePathForMember(config2, repoName, branch) {
37141
37186
  if (match)
37142
37187
  return match.path;
37143
37188
  const candidate = getWorktreePath(repo, branch);
37144
- if (fs37.existsSync(candidate))
37189
+ if (fs38.existsSync(candidate))
37145
37190
  return candidate;
37146
37191
  return null;
37147
37192
  }
@@ -37199,7 +37244,7 @@ function registerCreateSub(workspace) {
37199
37244
  process.exit(1);
37200
37245
  }
37201
37246
  const wsPath = workspacePathFor2(config2, name);
37202
- if (fs37.existsSync(wsPath)) {
37247
+ if (fs38.existsSync(wsPath)) {
37203
37248
  stepError("Workspace already exists", wsPath);
37204
37249
  process.exit(1);
37205
37250
  }
@@ -37300,7 +37345,7 @@ function registerAddSub(workspace) {
37300
37345
  const globalOpts = workspace.parent.opts();
37301
37346
  const config2 = loadConfig();
37302
37347
  const wsPath = workspacePathFor2(config2, name);
37303
- if (!fs37.existsSync(wsPath)) {
37348
+ if (!fs38.existsSync(wsPath)) {
37304
37349
  stepError("Workspace not found", wsPath);
37305
37350
  process.exit(1);
37306
37351
  }
@@ -37336,7 +37381,7 @@ function registerRmSub(workspace) {
37336
37381
  const globalOpts = workspace.parent.opts();
37337
37382
  const config2 = loadConfig();
37338
37383
  const wsPath = workspacePathFor2(config2, name);
37339
- if (!fs37.existsSync(wsPath)) {
37384
+ if (!fs38.existsSync(wsPath)) {
37340
37385
  stepError("Workspace not found", wsPath);
37341
37386
  process.exit(1);
37342
37387
  }
@@ -37358,7 +37403,7 @@ function registerRemoveSub(workspace) {
37358
37403
  const globalOpts = workspace.parent.opts();
37359
37404
  const config2 = loadConfig();
37360
37405
  const wsPath = workspacePathFor2(config2, name);
37361
- if (!fs37.existsSync(wsPath)) {
37406
+ if (!fs38.existsSync(wsPath)) {
37362
37407
  stepError("Workspace not found", wsPath);
37363
37408
  process.exit(1);
37364
37409
  }
@@ -37372,7 +37417,7 @@ function registerRemoveSub(workspace) {
37372
37417
  return;
37373
37418
  }
37374
37419
  try {
37375
- fs37.rmSync(wsPath, { recursive: true, force: true });
37420
+ fs38.rmSync(wsPath, { recursive: true, force: true });
37376
37421
  stepSuccess("Workspace removed", wsPath);
37377
37422
  summary(`Done — workspace ${name} deleted (member worktrees preserved)`);
37378
37423
  } catch (err) {
@@ -37385,7 +37430,7 @@ function registerVerifySub(workspace) {
37385
37430
  workspace.command("verify <name>").description("Verify that every workspace member link resolves").action(async (name) => {
37386
37431
  const config2 = loadConfig();
37387
37432
  const wsPath = workspacePathFor2(config2, name);
37388
- if (!fs37.existsSync(wsPath)) {
37433
+ if (!fs38.existsSync(wsPath)) {
37389
37434
  stepError("Workspace not found", wsPath);
37390
37435
  process.exit(1);
37391
37436
  }
@@ -37420,7 +37465,7 @@ init_config();
37420
37465
  init_resolver();
37421
37466
  init_log();
37422
37467
  init_history();
37423
- import fs38 from "fs";
37468
+ import fs39 from "fs";
37424
37469
  var program2 = new Command;
37425
37470
  program2.name("wtx").description("Multi-repo git worktree manager").version(VERSION).option("-q, --quiet", "Suppress progress indicators", false).option("--verbose", "Show git commands as they run", false).option("--dry-run", "Show what would happen", false);
37426
37471
  var MUTATING_COMMANDS = new Set([
@@ -37530,7 +37575,7 @@ program2.command("_resolve-path <repo> <branch>", { hidden: true }).description(
37530
37575
  }
37531
37576
  const repo = repos[0];
37532
37577
  const wtPath = getWorktreePath(repo, branch);
37533
- if (!fs38.existsSync(wtPath)) {
37578
+ if (!fs39.existsSync(wtPath)) {
37534
37579
  process.stderr.write(`✗ No worktree at ${wtPath}
37535
37580
  `);
37536
37581
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogpoyraz/wtx",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "Multi-repo git worktree manager",
5
5
  "type": "module",
6
6
  "bin": {