@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 +2 -1
- package/completions/_wtx.zsh +8 -0
- package/completions/wtx.bash +1 -0
- package/completions/wtx.fish +3 -0
- package/dist/cli.mjs +209 -164
- package/package.json +1 -1
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
|
|---|---|
|
package/completions/_wtx.zsh
CHANGED
|
@@ -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
|
package/completions/wtx.bash
CHANGED
|
@@ -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" ;;
|
package/completions/wtx.fish
CHANGED
|
@@ -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
|
|
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
|
|
25122
|
+
await fs24.promises.writeFile(tmpPath, `${JSON.stringify(manifest, null, 2)}
|
|
25123
25123
|
`, "utf8");
|
|
25124
|
-
await
|
|
25124
|
+
await fs24.promises.rename(tmpPath, manifestPath);
|
|
25125
25125
|
}
|
|
25126
25126
|
async function readManifest(workspacePath) {
|
|
25127
|
-
const raw = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
28437
|
+
fs35.writeFileSync(tmpPath, kept.length > 0 ? `${kept.join(`
|
|
28437
28438
|
`)}
|
|
28438
28439
|
` : "", "utf-8");
|
|
28439
|
-
|
|
28440
|
+
fs35.renameSync(tmpPath, historyPath);
|
|
28440
28441
|
} catch (err) {
|
|
28441
|
-
if (
|
|
28442
|
-
|
|
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 (!
|
|
28451
|
-
|
|
28451
|
+
if (!fs35.existsSync(dir)) {
|
|
28452
|
+
fs35.mkdirSync(dir, { recursive: true });
|
|
28452
28453
|
}
|
|
28453
28454
|
rotateHistory();
|
|
28454
|
-
|
|
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 =
|
|
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
|
|
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) ||
|
|
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
|
|
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
|
|
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 &&
|
|
34281
|
-
|
|
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
|
-
|
|
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 &&
|
|
34518
|
-
|
|
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 && (
|
|
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) || !
|
|
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 && !
|
|
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
|
|
34683
|
-
|
|
34684
|
-
|
|
34685
|
-
|
|
34686
|
-
|
|
34687
|
-
|
|
34688
|
-
|
|
34689
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 (
|
|
35460
|
-
const mainContent =
|
|
35461
|
-
const wtContent =
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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 (!
|
|
35781
|
+
if (!fs31.existsSync(path34.join(repo.mainPath, entry)))
|
|
35742
35782
|
continue;
|
|
35743
35783
|
const destPath = path34.join(planned.newPath, entry);
|
|
35744
|
-
if (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
36459
|
+
if (fs36.existsSync(p) && fs36.statSync(p).isFile())
|
|
36420
36460
|
return p;
|
|
36421
|
-
} else if (
|
|
36422
|
-
const stat =
|
|
36461
|
+
} else if (fs36.existsSync(p)) {
|
|
36462
|
+
const stat = fs36.statSync(p);
|
|
36423
36463
|
if (stat.isFile()) {
|
|
36424
|
-
const head =
|
|
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(
|
|
36467
|
+
const dir = path39.dirname(fs36.realpathSync(p));
|
|
36428
36468
|
const cli = path39.join(dir, "cli.mjs");
|
|
36429
|
-
if (
|
|
36469
|
+
if (fs36.existsSync(cli))
|
|
36430
36470
|
return cli;
|
|
36431
36471
|
const cli2 = path39.resolve(dir, "../dist/cli.mjs");
|
|
36432
|
-
if (
|
|
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 (
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 &&
|
|
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
|
|
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 (!
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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 (!
|
|
37578
|
+
if (!fs39.existsSync(wtPath)) {
|
|
37534
37579
|
process.stderr.write(`✗ No worktree at ${wtPath}
|
|
37535
37580
|
`);
|
|
37536
37581
|
process.exit(1);
|