@ogpoyraz/wtx 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.mjs +446 -235
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -280,7 +280,7 @@ Lookups degrade gracefully: if `gh` is missing or unauthenticated you get a warn
|
|
|
280
280
|
| `c` | Edit configuration |
|
|
281
281
|
| `?` / `q` | Help / quit |
|
|
282
282
|
|
|
283
|
-
Actions
|
|
283
|
+
Actions run in the background — navigation never locks. Progress shows inline: `fetching` next to the repo header, `deleting` / `rebasing` / `syncing` next to the branch (dimmed while busy), and a new worktree appears immediately as a `(creating)` row. One operation runs per repo at a time; conflicting actions are rejected with a toast until it finishes. On failure a log modal opens with the captured output; press any key to dismiss. Destructive ones confirm first.
|
|
284
284
|
|
|
285
285
|
---
|
|
286
286
|
|
package/dist/cli.mjs
CHANGED
|
@@ -24910,7 +24910,7 @@ var init_deps = __esm(() => {
|
|
|
24910
24910
|
});
|
|
24911
24911
|
|
|
24912
24912
|
// src/tui/data.ts
|
|
24913
|
-
async function fetchWorktreeData(opts) {
|
|
24913
|
+
async function fetchWorktreeData(opts, scope) {
|
|
24914
24914
|
const warnings = [];
|
|
24915
24915
|
let config2;
|
|
24916
24916
|
try {
|
|
@@ -24924,6 +24924,10 @@ async function fetchWorktreeData(opts) {
|
|
|
24924
24924
|
} catch (err) {
|
|
24925
24925
|
throw new Error(`Failed to resolve repos: ${err.message}`);
|
|
24926
24926
|
}
|
|
24927
|
+
if (scope) {
|
|
24928
|
+
const scopeSet = new Set(scope);
|
|
24929
|
+
repos = repos.filter((r) => scopeSet.has(r.name));
|
|
24930
|
+
}
|
|
24927
24931
|
const semaphore = new Semaphore(4);
|
|
24928
24932
|
const allRows = [];
|
|
24929
24933
|
const processRepo = async (repo) => {
|
|
@@ -24941,7 +24945,7 @@ async function fetchWorktreeData(opts) {
|
|
|
24941
24945
|
prCache.set(`${repo.name}/${br}`, pr);
|
|
24942
24946
|
}
|
|
24943
24947
|
} catch (err) {
|
|
24944
|
-
warnings.push(`PR lookup failed for ${repo.name}: ${err.message}`);
|
|
24948
|
+
warnings.push({ repoName: repo.name, message: `PR lookup failed for ${repo.name}: ${err.message}` });
|
|
24945
24949
|
for (const br of branches) {
|
|
24946
24950
|
const cached2 = prCache.get(`${repo.name}/${br}`);
|
|
24947
24951
|
if (cached2) {
|
|
@@ -25043,7 +25047,7 @@ async function fetchWorktreeData(opts) {
|
|
|
25043
25047
|
allRows.push(row);
|
|
25044
25048
|
}
|
|
25045
25049
|
} catch (err) {
|
|
25046
|
-
warnings.push(`Failed to process repo ${repo.name}: ${err.message}`);
|
|
25050
|
+
warnings.push({ repoName: repo.name, message: `Failed to process repo ${repo.name}: ${err.message}` });
|
|
25047
25051
|
} finally {
|
|
25048
25052
|
semaphore.release();
|
|
25049
25053
|
}
|
|
@@ -25066,20 +25070,131 @@ var init_data = __esm(() => {
|
|
|
25066
25070
|
prCache = new Map;
|
|
25067
25071
|
});
|
|
25068
25072
|
|
|
25073
|
+
// src/tui/utils.ts
|
|
25074
|
+
function matchesFilter(entry, term) {
|
|
25075
|
+
if (!term)
|
|
25076
|
+
return true;
|
|
25077
|
+
const lower = term.toLowerCase();
|
|
25078
|
+
if (entry.branch.toLowerCase().includes(lower))
|
|
25079
|
+
return true;
|
|
25080
|
+
if (entry.repoName.toLowerCase().includes(lower))
|
|
25081
|
+
return true;
|
|
25082
|
+
if (entry.prNumber?.toString().includes(lower))
|
|
25083
|
+
return true;
|
|
25084
|
+
if (entry.owner?.toLowerCase().includes(lower))
|
|
25085
|
+
return true;
|
|
25086
|
+
if (entry.prState?.toLowerCase().includes(lower))
|
|
25087
|
+
return true;
|
|
25088
|
+
if (entry.prUrl?.toLowerCase().includes(lower))
|
|
25089
|
+
return true;
|
|
25090
|
+
return false;
|
|
25091
|
+
}
|
|
25092
|
+
function toggleSelection(current, path33) {
|
|
25093
|
+
const next = new Set(current);
|
|
25094
|
+
if (next.has(path33)) {
|
|
25095
|
+
next.delete(path33);
|
|
25096
|
+
} else {
|
|
25097
|
+
next.add(path33);
|
|
25098
|
+
}
|
|
25099
|
+
return next;
|
|
25100
|
+
}
|
|
25101
|
+
function computeScrollWindow(selectedIndex, currentStart, visibleRows, totalRows) {
|
|
25102
|
+
if (totalRows === 0)
|
|
25103
|
+
return { start: 0, end: 0 };
|
|
25104
|
+
let start = currentStart;
|
|
25105
|
+
const maxStart = Math.max(0, totalRows - visibleRows);
|
|
25106
|
+
if (start > maxStart) {
|
|
25107
|
+
start = maxStart;
|
|
25108
|
+
}
|
|
25109
|
+
if (selectedIndex < start) {
|
|
25110
|
+
start = selectedIndex;
|
|
25111
|
+
} else if (selectedIndex >= start + visibleRows) {
|
|
25112
|
+
start = selectedIndex - visibleRows + 1;
|
|
25113
|
+
}
|
|
25114
|
+
const end = Math.min(start + visibleRows, totalRows);
|
|
25115
|
+
return { start, end };
|
|
25116
|
+
}
|
|
25117
|
+
function rowSort(a2, b) {
|
|
25118
|
+
if (a2.isMainCheckout && !b.isMainCheckout)
|
|
25119
|
+
return -1;
|
|
25120
|
+
if (!a2.isMainCheckout && b.isMainCheckout)
|
|
25121
|
+
return 1;
|
|
25122
|
+
return a2.branch.localeCompare(b.branch);
|
|
25123
|
+
}
|
|
25124
|
+
function mergeBlocks(prev, next, scope) {
|
|
25125
|
+
if (!scope)
|
|
25126
|
+
return next;
|
|
25127
|
+
const kept = prev.filter((b) => !scope.has(b.repoName));
|
|
25128
|
+
return [...kept, ...next].sort((a2, b) => a2.repoName.localeCompare(b.repoName));
|
|
25129
|
+
}
|
|
25130
|
+
function mergeWarnings(prev, next, scope) {
|
|
25131
|
+
if (!scope)
|
|
25132
|
+
return next;
|
|
25133
|
+
const kept = prev.filter((w) => !scope.has(w.repoName));
|
|
25134
|
+
return [...kept, ...next];
|
|
25135
|
+
}
|
|
25136
|
+
function makePlaceholderRow(repoName, branch) {
|
|
25137
|
+
return {
|
|
25138
|
+
repoName,
|
|
25139
|
+
branch,
|
|
25140
|
+
path: `pending-create:${repoName}:${branch}`,
|
|
25141
|
+
commitShort: "",
|
|
25142
|
+
isMainCheckout: false,
|
|
25143
|
+
isLocked: false,
|
|
25144
|
+
isPrunable: false,
|
|
25145
|
+
isBare: false,
|
|
25146
|
+
dirtyFiles: [],
|
|
25147
|
+
ahead: null,
|
|
25148
|
+
behind: null,
|
|
25149
|
+
prNumber: null,
|
|
25150
|
+
prState: null,
|
|
25151
|
+
prChecks: null,
|
|
25152
|
+
prUrl: null,
|
|
25153
|
+
owner: null,
|
|
25154
|
+
rebaseStatus: null,
|
|
25155
|
+
depsStrategy: "none",
|
|
25156
|
+
isPendingCreate: true
|
|
25157
|
+
};
|
|
25158
|
+
}
|
|
25159
|
+
function withCreatePlaceholders(blocks, creating) {
|
|
25160
|
+
if (creating.length === 0)
|
|
25161
|
+
return blocks;
|
|
25162
|
+
const byRepo = new Map;
|
|
25163
|
+
for (const c4 of creating) {
|
|
25164
|
+
const arr = byRepo.get(c4.repoName);
|
|
25165
|
+
if (arr) {
|
|
25166
|
+
arr.push(c4.branch);
|
|
25167
|
+
} else {
|
|
25168
|
+
byRepo.set(c4.repoName, [c4.branch]);
|
|
25169
|
+
}
|
|
25170
|
+
}
|
|
25171
|
+
return blocks.map((block) => {
|
|
25172
|
+
const branches = byRepo.get(block.repoName);
|
|
25173
|
+
if (!branches)
|
|
25174
|
+
return block;
|
|
25175
|
+
const placeholders = branches.map((br) => makePlaceholderRow(block.repoName, br));
|
|
25176
|
+
return { ...block, rows: [...block.rows, ...placeholders].sort(rowSort) };
|
|
25177
|
+
});
|
|
25178
|
+
}
|
|
25179
|
+
|
|
25069
25180
|
// src/tui/hooks/useWorktrees.ts
|
|
25070
|
-
import { useState, useEffect, useCallback } from "react";
|
|
25181
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
25071
25182
|
function useWorktrees(opts) {
|
|
25072
25183
|
const [blocks, setBlocks] = useState([]);
|
|
25073
25184
|
const [loading, setLoading] = useState(true);
|
|
25185
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
25074
25186
|
const [error52, setError] = useState(null);
|
|
25075
25187
|
const [warnings, setWarnings] = useState([]);
|
|
25076
25188
|
const [lastRefreshed, setLastRefreshed] = useState("");
|
|
25077
|
-
const
|
|
25078
|
-
|
|
25189
|
+
const seqRef = useRef(0);
|
|
25190
|
+
const refresh = useCallback(async (scope) => {
|
|
25191
|
+
const seq = ++seqRef.current;
|
|
25192
|
+
setRefreshing(true);
|
|
25079
25193
|
setError(null);
|
|
25080
|
-
setWarnings([]);
|
|
25081
25194
|
try {
|
|
25082
|
-
const data = await fetchWorktreeData(opts);
|
|
25195
|
+
const data = await fetchWorktreeData(opts, scope);
|
|
25196
|
+
if (seq !== seqRef.current)
|
|
25197
|
+
return;
|
|
25083
25198
|
const byRepo = new Map;
|
|
25084
25199
|
for (const row of data.rows) {
|
|
25085
25200
|
let arr = byRepo.get(row.repoName);
|
|
@@ -25091,29 +25206,27 @@ function useWorktrees(opts) {
|
|
|
25091
25206
|
}
|
|
25092
25207
|
const newBlocks = [];
|
|
25093
25208
|
for (const [repoName, rows] of byRepo.entries()) {
|
|
25094
|
-
rows.sort(
|
|
25095
|
-
if (a2.isMainCheckout && !b.isMainCheckout)
|
|
25096
|
-
return -1;
|
|
25097
|
-
if (!a2.isMainCheckout && b.isMainCheckout)
|
|
25098
|
-
return 1;
|
|
25099
|
-
return a2.branch.localeCompare(b.branch);
|
|
25100
|
-
});
|
|
25209
|
+
rows.sort(rowSort);
|
|
25101
25210
|
newBlocks.push({ repoName, rows });
|
|
25102
25211
|
}
|
|
25103
|
-
|
|
25104
|
-
setBlocks(newBlocks);
|
|
25105
|
-
setWarnings(data.warnings);
|
|
25212
|
+
const scopeSet = scope ? new Set(scope) : undefined;
|
|
25213
|
+
setBlocks((prev) => mergeBlocks(prev, newBlocks, scopeSet));
|
|
25214
|
+
setWarnings((prev) => mergeWarnings(prev, data.warnings, scopeSet));
|
|
25106
25215
|
setLastRefreshed(new Date().toLocaleTimeString());
|
|
25107
25216
|
} catch (err) {
|
|
25108
|
-
|
|
25217
|
+
if (seq === seqRef.current)
|
|
25218
|
+
setError(err.message);
|
|
25109
25219
|
} finally {
|
|
25110
|
-
|
|
25220
|
+
if (seq === seqRef.current) {
|
|
25221
|
+
setLoading(false);
|
|
25222
|
+
setRefreshing(false);
|
|
25223
|
+
}
|
|
25111
25224
|
}
|
|
25112
25225
|
}, [opts]);
|
|
25113
25226
|
useEffect(() => {
|
|
25114
25227
|
refresh();
|
|
25115
25228
|
}, [refresh]);
|
|
25116
|
-
return { blocks, loading, error: error52, warnings, lastRefreshed, refresh };
|
|
25229
|
+
return { blocks, loading, refreshing, error: error52, warnings, lastRefreshed, refresh };
|
|
25117
25230
|
}
|
|
25118
25231
|
var init_useWorktrees = __esm(() => {
|
|
25119
25232
|
init_data();
|
|
@@ -25151,7 +25264,7 @@ var init_theme = __esm(() => {
|
|
|
25151
25264
|
});
|
|
25152
25265
|
|
|
25153
25266
|
// src/tui/components/WorktreeTable.tsx
|
|
25154
|
-
import { useEffect as useEffect2, useRef } from "react";
|
|
25267
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
25155
25268
|
import { jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
25156
25269
|
function statusBadge(row) {
|
|
25157
25270
|
if (row.isMainCheckout)
|
|
@@ -25166,9 +25279,10 @@ function statusBadge(row) {
|
|
|
25166
25279
|
return { text: `dirty (${row.dirtyFiles.length})`, fg: tokens.warning };
|
|
25167
25280
|
return { text: "clean", fg: tokens.dim };
|
|
25168
25281
|
}
|
|
25169
|
-
function WorktreeItem({ row, isSelected, isMultiSelected,
|
|
25170
|
-
const badge =
|
|
25171
|
-
const
|
|
25282
|
+
function WorktreeItem({ row, isSelected, isMultiSelected, indicator, frame, id }) {
|
|
25283
|
+
const badge = indicator ? indicator.running ? { text: `${frame} ${indicator.verb}…`, fg: tokens.accent } : { text: `◌ ${indicator.verb}`, fg: tokens.dim } : row.isPendingCreate ? { text: `${frame} creating…`, fg: tokens.accent } : statusBadge(row);
|
|
25284
|
+
const disabled = indicator !== undefined || row.isPendingCreate === true;
|
|
25285
|
+
const primary = isSelected ? tokens.bright : disabled ? tokens.dim : tokens.fg;
|
|
25172
25286
|
const divergence = row.ahead !== null && row.behind !== null && (row.ahead > 0 || row.behind > 0) ? ` · ↑${row.ahead} ↓${row.behind}` : "";
|
|
25173
25287
|
const prSegment = row.prNumber ? [
|
|
25174
25288
|
`· #${row.prNumber}`,
|
|
@@ -25224,8 +25338,8 @@ function WorktreeItem({ row, isSelected, isMultiSelected, busy, id }) {
|
|
|
25224
25338
|
]
|
|
25225
25339
|
});
|
|
25226
25340
|
}
|
|
25227
|
-
function WorktreeTable({ blocks, selectedIndex, selection = new Set,
|
|
25228
|
-
const scrollRef =
|
|
25341
|
+
function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repoVerbs, rowVerbs }) {
|
|
25342
|
+
const scrollRef = useRef2(null);
|
|
25229
25343
|
useEffect2(() => {
|
|
25230
25344
|
if (scrollRef.current?.scrollChildIntoView) {
|
|
25231
25345
|
scrollRef.current.scrollChildIntoView("selected-row");
|
|
@@ -25245,42 +25359,48 @@ function WorktreeTable({ blocks, selectedIndex, selection = new Set, busy }) {
|
|
|
25245
25359
|
children: blocks.length === 0 ? /* @__PURE__ */ jsx("text", {
|
|
25246
25360
|
fg: tokens.dim,
|
|
25247
25361
|
children: "No repositories configured."
|
|
25248
|
-
}) : blocks.map((block) =>
|
|
25249
|
-
|
|
25250
|
-
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
|
|
25254
|
-
|
|
25255
|
-
|
|
25256
|
-
|
|
25257
|
-
|
|
25258
|
-
|
|
25259
|
-
|
|
25260
|
-
|
|
25261
|
-
|
|
25262
|
-
|
|
25263
|
-
|
|
25264
|
-
|
|
25265
|
-
|
|
25266
|
-
|
|
25267
|
-
|
|
25268
|
-
|
|
25362
|
+
}) : blocks.map((block) => {
|
|
25363
|
+
const repoIndicator = repoVerbs.get(block.repoName);
|
|
25364
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
25365
|
+
flexDirection: "column",
|
|
25366
|
+
style: { marginBottom: 1 },
|
|
25367
|
+
children: [
|
|
25368
|
+
/* @__PURE__ */ jsx("box", {
|
|
25369
|
+
style: { marginTop: flatIndex === 0 ? 0 : 1 },
|
|
25370
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
25371
|
+
children: [
|
|
25372
|
+
/* @__PURE__ */ jsx("span", {
|
|
25373
|
+
fg: tokens.bright,
|
|
25374
|
+
children: block.repoName
|
|
25375
|
+
}),
|
|
25376
|
+
/* @__PURE__ */ jsx("span", {
|
|
25377
|
+
fg: tokens.dim,
|
|
25378
|
+
children: ` · ${block.rows.filter((r) => !r.isPendingCreate).length}`
|
|
25379
|
+
}),
|
|
25380
|
+
repoIndicator && /* @__PURE__ */ jsx("span", {
|
|
25381
|
+
fg: repoIndicator.running ? tokens.accent : tokens.dim,
|
|
25382
|
+
children: repoIndicator.running ? ` ${frame} ${repoIndicator.verb}…` : ` ◌ ${repoIndicator.verb}`
|
|
25383
|
+
})
|
|
25384
|
+
]
|
|
25385
|
+
})
|
|
25386
|
+
}),
|
|
25387
|
+
block.rows.map((row) => {
|
|
25388
|
+
const navigable = !row.isPendingCreate;
|
|
25389
|
+
const isSelected = navigable && flatIndex === selectedIndex;
|
|
25390
|
+
if (navigable)
|
|
25391
|
+
flatIndex++;
|
|
25392
|
+
return /* @__PURE__ */ jsx(WorktreeItem, {
|
|
25393
|
+
row,
|
|
25394
|
+
isSelected,
|
|
25395
|
+
isMultiSelected: selection.has(row.path),
|
|
25396
|
+
indicator: rowVerbs.get(row.path),
|
|
25397
|
+
frame,
|
|
25398
|
+
id: isSelected ? "selected-row" : undefined
|
|
25399
|
+
}, row.path);
|
|
25269
25400
|
})
|
|
25270
|
-
|
|
25271
|
-
|
|
25272
|
-
|
|
25273
|
-
flatIndex++;
|
|
25274
|
-
return /* @__PURE__ */ jsx(WorktreeItem, {
|
|
25275
|
-
row,
|
|
25276
|
-
isSelected,
|
|
25277
|
-
isMultiSelected: selection.has(row.path),
|
|
25278
|
-
busy: busy?.rowPath === row.path ? busy : undefined,
|
|
25279
|
-
id: isSelected ? "selected-row" : undefined
|
|
25280
|
-
}, row.path);
|
|
25281
|
-
})
|
|
25282
|
-
]
|
|
25283
|
-
}, block.repoName))
|
|
25401
|
+
]
|
|
25402
|
+
}, block.repoName);
|
|
25403
|
+
})
|
|
25284
25404
|
});
|
|
25285
25405
|
}
|
|
25286
25406
|
var SECONDARY_INDENT = " ";
|
|
@@ -25949,7 +26069,7 @@ async function runWtxAction(args, onLine) {
|
|
|
25949
26069
|
|
|
25950
26070
|
// src/tui/components/ActionLogModal.tsx
|
|
25951
26071
|
import { jsx as jsx7, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
|
|
25952
|
-
function ActionLogModal({ title, lines, done, exitCode }) {
|
|
26072
|
+
function ActionLogModal({ title, lines, done, exitCode, remaining }) {
|
|
25953
26073
|
return /* @__PURE__ */ jsxs6(Overlay, {
|
|
25954
26074
|
title,
|
|
25955
26075
|
borderColor: tokens.border,
|
|
@@ -25989,6 +26109,10 @@ function ActionLogModal({ title, lines, done, exitCode }) {
|
|
|
25989
26109
|
exitCode
|
|
25990
26110
|
]
|
|
25991
26111
|
}),
|
|
26112
|
+
remaining !== undefined && remaining > 0 && /* @__PURE__ */ jsx7("span", {
|
|
26113
|
+
fg: tokens.warning,
|
|
26114
|
+
children: ` · ${remaining} more failure${remaining > 1 ? "s" : ""}`
|
|
26115
|
+
}),
|
|
25992
26116
|
/* @__PURE__ */ jsx7("span", {
|
|
25993
26117
|
fg: tokens.dim,
|
|
25994
26118
|
children: " - press any key to close"
|
|
@@ -26200,51 +26324,6 @@ var init_InputModal = __esm(() => {
|
|
|
26200
26324
|
init_theme();
|
|
26201
26325
|
});
|
|
26202
26326
|
|
|
26203
|
-
// src/tui/utils.ts
|
|
26204
|
-
function matchesFilter(entry, term) {
|
|
26205
|
-
if (!term)
|
|
26206
|
-
return true;
|
|
26207
|
-
const lower = term.toLowerCase();
|
|
26208
|
-
if (entry.branch.toLowerCase().includes(lower))
|
|
26209
|
-
return true;
|
|
26210
|
-
if (entry.repoName.toLowerCase().includes(lower))
|
|
26211
|
-
return true;
|
|
26212
|
-
if (entry.prNumber?.toString().includes(lower))
|
|
26213
|
-
return true;
|
|
26214
|
-
if (entry.owner?.toLowerCase().includes(lower))
|
|
26215
|
-
return true;
|
|
26216
|
-
if (entry.prState?.toLowerCase().includes(lower))
|
|
26217
|
-
return true;
|
|
26218
|
-
if (entry.prUrl?.toLowerCase().includes(lower))
|
|
26219
|
-
return true;
|
|
26220
|
-
return false;
|
|
26221
|
-
}
|
|
26222
|
-
function toggleSelection(current, path34) {
|
|
26223
|
-
const next = new Set(current);
|
|
26224
|
-
if (next.has(path34)) {
|
|
26225
|
-
next.delete(path34);
|
|
26226
|
-
} else {
|
|
26227
|
-
next.add(path34);
|
|
26228
|
-
}
|
|
26229
|
-
return next;
|
|
26230
|
-
}
|
|
26231
|
-
function computeScrollWindow(selectedIndex, currentStart, visibleRows, totalRows) {
|
|
26232
|
-
if (totalRows === 0)
|
|
26233
|
-
return { start: 0, end: 0 };
|
|
26234
|
-
let start = currentStart;
|
|
26235
|
-
const maxStart = Math.max(0, totalRows - visibleRows);
|
|
26236
|
-
if (start > maxStart) {
|
|
26237
|
-
start = maxStart;
|
|
26238
|
-
}
|
|
26239
|
-
if (selectedIndex < start) {
|
|
26240
|
-
start = selectedIndex;
|
|
26241
|
-
} else if (selectedIndex >= start + visibleRows) {
|
|
26242
|
-
start = selectedIndex - visibleRows + 1;
|
|
26243
|
-
}
|
|
26244
|
-
const end = Math.min(start + visibleRows, totalRows);
|
|
26245
|
-
return { start, end };
|
|
26246
|
-
}
|
|
26247
|
-
|
|
26248
26327
|
// src/tui/components/ConfigOverlay.tsx
|
|
26249
26328
|
import { useState as useState4, useEffect as useEffect4 } from "react";
|
|
26250
26329
|
import { useKeyboard } from "@opentui/react";
|
|
@@ -26634,16 +26713,19 @@ var init_useSpinnerFrame = __esm(() => {
|
|
|
26634
26713
|
});
|
|
26635
26714
|
|
|
26636
26715
|
// src/tui/components/App.tsx
|
|
26637
|
-
import { useState as useState6, useEffect as useEffect6 } from "react";
|
|
26716
|
+
import { useState as useState6, useEffect as useEffect6, useMemo, useRef as useRef3, useCallback as useCallback2 } from "react";
|
|
26638
26717
|
import { useKeyboard as useKeyboard2, useRenderer } from "@opentui/react";
|
|
26639
26718
|
import { jsx as jsx11, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
|
|
26640
26719
|
function App({ opts }) {
|
|
26641
26720
|
const renderer = useRenderer();
|
|
26642
|
-
const { blocks, loading, error: error52, warnings, lastRefreshed, refresh } = useWorktrees(opts);
|
|
26721
|
+
const { blocks, loading, refreshing, error: error52, warnings, lastRefreshed, refresh } = useWorktrees(opts);
|
|
26643
26722
|
const [selectedIndex, setSelectedIndex] = useState6(0);
|
|
26644
26723
|
const [modal, setModal] = useState6({ type: "none" });
|
|
26645
26724
|
const [actionMessage, setActionMessage] = useState6();
|
|
26646
|
-
const
|
|
26725
|
+
const messageTimer = useRef3(null);
|
|
26726
|
+
const [ops, setOps] = useState6([]);
|
|
26727
|
+
const [failedLogs, setFailedLogs] = useState6([]);
|
|
26728
|
+
const nextOpId = useRef3(1);
|
|
26647
26729
|
const [createModal, setCreateModal] = useState6(false);
|
|
26648
26730
|
const [createError, setCreateError] = useState6();
|
|
26649
26731
|
const [configOpen, setConfigOpen] = useState6(false);
|
|
@@ -26655,11 +26737,19 @@ function App({ opts }) {
|
|
|
26655
26737
|
setModal({ type: "error", message: error52 });
|
|
26656
26738
|
}
|
|
26657
26739
|
}, [error52]);
|
|
26658
|
-
const
|
|
26659
|
-
|
|
26660
|
-
|
|
26661
|
-
|
|
26662
|
-
|
|
26740
|
+
const flash = useCallback2((message, ms = 3000) => {
|
|
26741
|
+
if (messageTimer.current)
|
|
26742
|
+
clearTimeout(messageTimer.current);
|
|
26743
|
+
setActionMessage(message);
|
|
26744
|
+
messageTimer.current = setTimeout(() => setActionMessage(undefined), ms);
|
|
26745
|
+
}, []);
|
|
26746
|
+
const busyRepos = useMemo(() => new Set(ops.flatMap((o2) => o2.repoNames)), [ops]);
|
|
26747
|
+
const busyRowPaths = useMemo(() => new Set(ops.map((o2) => o2.rowPath).filter((p) => p !== undefined)), [ops]);
|
|
26748
|
+
const anyRunning = ops.some((o2) => o2.status === "running");
|
|
26749
|
+
const spinnerFrame = useSpinnerFrame(anyRunning || loading || refreshing);
|
|
26750
|
+
const baseFiltered = useMemo(() => blocks.map((b) => ({ ...b, rows: b.rows.filter((r) => matchesFilter(r, filterText)) })).filter((b) => b.rows.length > 0), [blocks, filterText]);
|
|
26751
|
+
const displayBlocks = useMemo(() => withCreatePlaceholders(baseFiltered, ops.filter((o2) => o2.branch !== undefined).map((o2) => ({ repoName: o2.repoNames[0], branch: o2.branch }))), [baseFiltered, ops]);
|
|
26752
|
+
const flatRows = useMemo(() => displayBlocks.flatMap((b) => b.rows.filter((r) => !r.isPendingCreate)), [displayBlocks]);
|
|
26663
26753
|
const totalRows = blocks.flatMap((b) => b.rows).length;
|
|
26664
26754
|
const maxIndex = Math.max(0, flatRows.length - 1);
|
|
26665
26755
|
useEffect6(() => {
|
|
@@ -26668,50 +26758,118 @@ function App({ opts }) {
|
|
|
26668
26758
|
}
|
|
26669
26759
|
}, [maxIndex, selectedIndex]);
|
|
26670
26760
|
const selectedRow = flatRows[selectedIndex] ?? null;
|
|
26671
|
-
const
|
|
26672
|
-
|
|
26761
|
+
const { repoVerbs, rowVerbs } = useMemo(() => {
|
|
26762
|
+
const rv = new Map;
|
|
26763
|
+
const nv = new Map;
|
|
26764
|
+
for (const o2 of ops) {
|
|
26765
|
+
const indicator = { verb: VERBS[o2.kind], running: o2.status === "running" };
|
|
26766
|
+
if (o2.rowPath) {
|
|
26767
|
+
nv.set(o2.rowPath, indicator);
|
|
26768
|
+
} else {
|
|
26769
|
+
for (const rn of o2.repoNames)
|
|
26770
|
+
rv.set(rn, indicator);
|
|
26771
|
+
}
|
|
26772
|
+
}
|
|
26773
|
+
return { repoVerbs: rv, rowVerbs: nv };
|
|
26774
|
+
}, [ops]);
|
|
26775
|
+
const runningOps = ops.filter((o2) => o2.status === "running");
|
|
26776
|
+
const latestOp = runningOps[runningOps.length - 1];
|
|
26673
26777
|
const getSelectedRows = () => {
|
|
26674
26778
|
if (selection.size > 0) {
|
|
26675
26779
|
return blocks.flatMap((b) => b.rows).filter((r) => selection.has(r.path));
|
|
26676
26780
|
}
|
|
26677
26781
|
return selectedRow ? [selectedRow] : [];
|
|
26678
26782
|
};
|
|
26679
|
-
const
|
|
26680
|
-
for (const
|
|
26681
|
-
|
|
26783
|
+
const findConflict = (targets) => {
|
|
26784
|
+
for (const t of targets) {
|
|
26785
|
+
if (busyRowPaths.has(t.path))
|
|
26786
|
+
return `${t.branch} is busy`;
|
|
26787
|
+
}
|
|
26788
|
+
for (const t of targets) {
|
|
26789
|
+
if (busyRepos.has(t.repoName))
|
|
26790
|
+
return `${t.repoName} is busy`;
|
|
26682
26791
|
}
|
|
26683
|
-
|
|
26792
|
+
return null;
|
|
26684
26793
|
};
|
|
26685
|
-
const
|
|
26686
|
-
|
|
26687
|
-
|
|
26688
|
-
|
|
26689
|
-
|
|
26690
|
-
|
|
26691
|
-
|
|
26692
|
-
|
|
26693
|
-
...prev,
|
|
26694
|
-
lines: [...prev.lines, { text, type }]
|
|
26695
|
-
};
|
|
26696
|
-
});
|
|
26697
|
-
}).then((result) => {
|
|
26698
|
-
if (result.exitCode === 0) {
|
|
26699
|
-
setActionRun(null);
|
|
26700
|
-
} else {
|
|
26701
|
-
setActionRun((prev) => {
|
|
26702
|
-
if (!prev)
|
|
26703
|
-
return prev;
|
|
26704
|
-
return {
|
|
26705
|
-
...prev,
|
|
26706
|
-
done: true,
|
|
26707
|
-
exitCode: result.exitCode
|
|
26708
|
-
};
|
|
26709
|
-
});
|
|
26710
|
-
}
|
|
26711
|
-
refresh();
|
|
26712
|
-
resolve2();
|
|
26794
|
+
const executeOp = async (op, args, refreshScope) => {
|
|
26795
|
+
setOps((prev) => prev.map((o2) => o2.id === op.id ? { ...o2, status: "running" } : o2));
|
|
26796
|
+
const collected = [];
|
|
26797
|
+
let exitCode;
|
|
26798
|
+
try {
|
|
26799
|
+
const result = await runWtxAction(args, (text, type) => {
|
|
26800
|
+
collected.push({ text, type });
|
|
26801
|
+
setOps((prev) => prev.map((o2) => o2.id === op.id ? { ...o2, lines: [...o2.lines, { text, type }] } : o2));
|
|
26713
26802
|
});
|
|
26803
|
+
exitCode = result.exitCode;
|
|
26804
|
+
} catch (err) {
|
|
26805
|
+
exitCode = 1;
|
|
26806
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26807
|
+
collected.push({ text: `Failed to run wtx ${args.join(" ")}: ${msg}`, type: "err" });
|
|
26808
|
+
}
|
|
26809
|
+
if (exitCode === 0) {
|
|
26810
|
+
if (refreshScope)
|
|
26811
|
+
await refresh(refreshScope);
|
|
26812
|
+
setOps((prev) => prev.filter((o2) => o2.id !== op.id));
|
|
26813
|
+
} else {
|
|
26814
|
+
setOps((prev) => prev.filter((o2) => o2.id !== op.id));
|
|
26815
|
+
setFailedLogs((prev) => [...prev, { title: op.title, lines: collected, exitCode }]);
|
|
26816
|
+
}
|
|
26817
|
+
};
|
|
26818
|
+
const startBatchActions = (kind, targets, argsFor) => {
|
|
26819
|
+
const created = targets.map((row) => {
|
|
26820
|
+
const op = {
|
|
26821
|
+
id: nextOpId.current++,
|
|
26822
|
+
kind,
|
|
26823
|
+
repoNames: [row.repoName],
|
|
26824
|
+
rowPath: row.path,
|
|
26825
|
+
label: row.branch,
|
|
26826
|
+
title: `${capitalize(VERBS[kind])} ${row.branch}`,
|
|
26827
|
+
status: "queued",
|
|
26828
|
+
lines: []
|
|
26829
|
+
};
|
|
26830
|
+
return { op, row };
|
|
26714
26831
|
});
|
|
26832
|
+
setOps((prev) => [...prev, ...created.map((c4) => c4.op)]);
|
|
26833
|
+
(async () => {
|
|
26834
|
+
for (const { op, row } of created) {
|
|
26835
|
+
await executeOp(op, argsFor(row), [row.repoName]);
|
|
26836
|
+
}
|
|
26837
|
+
setSelection(new Set);
|
|
26838
|
+
})();
|
|
26839
|
+
};
|
|
26840
|
+
const startFetch = (targets) => {
|
|
26841
|
+
const repoNames = [...new Set(targets.map((t) => t.repoName))];
|
|
26842
|
+
const conflict = repoNames.find((rn) => busyRepos.has(rn));
|
|
26843
|
+
if (conflict) {
|
|
26844
|
+
flash(`${conflict} is busy`);
|
|
26845
|
+
return;
|
|
26846
|
+
}
|
|
26847
|
+
const label = repoNames.length === 1 ? repoNames[0] : `${repoNames.length} repos`;
|
|
26848
|
+
const op = {
|
|
26849
|
+
id: nextOpId.current++,
|
|
26850
|
+
kind: "fetch",
|
|
26851
|
+
repoNames,
|
|
26852
|
+
label,
|
|
26853
|
+
title: `Fetch ${label}`,
|
|
26854
|
+
status: "queued",
|
|
26855
|
+
lines: []
|
|
26856
|
+
};
|
|
26857
|
+
setOps((prev) => [...prev, op]);
|
|
26858
|
+
executeOp(op, ["fetch", "--repo", repoNames.join(",")], repoNames).then(() => setSelection(new Set));
|
|
26859
|
+
};
|
|
26860
|
+
const startCreate = (branch, repoName) => {
|
|
26861
|
+
const op = {
|
|
26862
|
+
id: nextOpId.current++,
|
|
26863
|
+
kind: "create",
|
|
26864
|
+
repoNames: [repoName],
|
|
26865
|
+
branch,
|
|
26866
|
+
label: `${branch} in ${repoName}`,
|
|
26867
|
+
title: `Create ${branch}`,
|
|
26868
|
+
status: "queued",
|
|
26869
|
+
lines: []
|
|
26870
|
+
};
|
|
26871
|
+
setOps((prev) => [...prev, op]);
|
|
26872
|
+
executeOp(op, ["create", branch, "--repo", repoName], [repoName]);
|
|
26715
26873
|
};
|
|
26716
26874
|
useKeyboard2((key) => {
|
|
26717
26875
|
if (isFiltering) {
|
|
@@ -26724,11 +26882,8 @@ function App({ opts }) {
|
|
|
26724
26882
|
}
|
|
26725
26883
|
return;
|
|
26726
26884
|
}
|
|
26727
|
-
if (
|
|
26728
|
-
|
|
26729
|
-
}
|
|
26730
|
-
if (actionRun) {
|
|
26731
|
-
setActionRun(null);
|
|
26885
|
+
if (failedLogs.length > 0) {
|
|
26886
|
+
setFailedLogs((prev) => prev.slice(1));
|
|
26732
26887
|
return;
|
|
26733
26888
|
}
|
|
26734
26889
|
if (configOpen)
|
|
@@ -26755,16 +26910,16 @@ function App({ opts }) {
|
|
|
26755
26910
|
const action = modal.type;
|
|
26756
26911
|
setModal({ type: "none" });
|
|
26757
26912
|
if (action === "confirm_remove") {
|
|
26758
|
-
|
|
26913
|
+
startBatchActions("remove", rows, (r) => {
|
|
26759
26914
|
const args = ["remove", r.branch, "--repo", r.repoName, "--yes"];
|
|
26760
26915
|
if (r.dirtyFiles.length > 0)
|
|
26761
26916
|
args.push("--force");
|
|
26762
26917
|
return args;
|
|
26763
26918
|
});
|
|
26764
26919
|
} else if (action === "confirm_rebase") {
|
|
26765
|
-
|
|
26920
|
+
startBatchActions("rebase", rows, (r) => ["rebase", r.branch, "--repo", r.repoName]);
|
|
26766
26921
|
} else {
|
|
26767
|
-
|
|
26922
|
+
startBatchActions("sync", rows, (r) => ["sync", r.branch, "--repo", r.repoName]);
|
|
26768
26923
|
}
|
|
26769
26924
|
return;
|
|
26770
26925
|
}
|
|
@@ -26792,113 +26947,160 @@ function App({ opts }) {
|
|
|
26792
26947
|
}
|
|
26793
26948
|
return;
|
|
26794
26949
|
}
|
|
26795
|
-
if (key.name === "
|
|
26796
|
-
(
|
|
26797
|
-
const targets = getSelectedRows();
|
|
26798
|
-
if (targets.length === 0)
|
|
26799
|
-
return;
|
|
26800
|
-
const target = targets[0];
|
|
26801
|
-
if (!target)
|
|
26802
|
-
return;
|
|
26803
|
-
const startedAt = Date.now();
|
|
26804
|
-
try {
|
|
26805
|
-
const config2 = await loadConfig();
|
|
26806
|
-
const cmdTemplate = resolveAgentCommand("claude", config2.agents) ?? "claude";
|
|
26807
|
-
const result = await spawnAgentInWorktree(cmdTemplate, target.path, { repoName: target.repoName, branch: target.branch });
|
|
26808
|
-
appendHistory({
|
|
26809
|
-
ts: new Date().toISOString(),
|
|
26810
|
-
source: "terminal",
|
|
26811
|
-
command: "agent",
|
|
26812
|
-
args: ["agent", target.branch, "--repo", target.repoName],
|
|
26813
|
-
durationMs: Date.now() - startedAt,
|
|
26814
|
-
exit: 0
|
|
26815
|
-
});
|
|
26816
|
-
setActionMessage(`Agent spawned (${result.mode}${result.session ? ` session ${result.session}` : ""})`);
|
|
26817
|
-
setTimeout(() => setActionMessage(undefined), 5000);
|
|
26818
|
-
} catch (e) {
|
|
26819
|
-
appendHistory({
|
|
26820
|
-
ts: new Date().toISOString(),
|
|
26821
|
-
source: "terminal",
|
|
26822
|
-
command: "agent",
|
|
26823
|
-
args: ["agent", target.branch, "--repo", target.repoName],
|
|
26824
|
-
durationMs: Date.now() - startedAt,
|
|
26825
|
-
exit: 1
|
|
26826
|
-
});
|
|
26827
|
-
setActionMessage(`Agent failed: ${e.message}`);
|
|
26828
|
-
}
|
|
26829
|
-
})();
|
|
26950
|
+
if (key.name === "down" || key.name === "j") {
|
|
26951
|
+
setSelectedIndex((prev) => Math.min(prev + 1, maxIndex));
|
|
26830
26952
|
return;
|
|
26831
26953
|
}
|
|
26832
|
-
if (key.name === "
|
|
26833
|
-
const targets = getSelectedRows();
|
|
26834
|
-
if (targets.length === 0)
|
|
26835
|
-
return;
|
|
26836
|
-
const repoNames = [...new Set(targets.map((t) => t.repoName))];
|
|
26837
|
-
const label = repoNames.length === 1 ? repoNames[0] : `${repoNames.length} repos`;
|
|
26838
|
-
startAction("fetch", `Fetch ${label}`, label, repoNames, undefined, ["fetch", "--repo", repoNames.join(",")]).then(() => setSelection(new Set));
|
|
26839
|
-
return;
|
|
26840
|
-
} else if (key.name === "down" || key.name === "j") {
|
|
26841
|
-
setSelectedIndex((prev) => Math.min(prev + 1, maxIndex));
|
|
26842
|
-
} else if (key.name === "up" || key.name === "k") {
|
|
26954
|
+
if (key.name === "up" || key.name === "k") {
|
|
26843
26955
|
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
26844
|
-
|
|
26956
|
+
return;
|
|
26957
|
+
}
|
|
26958
|
+
if (key.name === "r" && !key.shift) {
|
|
26845
26959
|
refresh();
|
|
26846
|
-
|
|
26960
|
+
return;
|
|
26961
|
+
}
|
|
26962
|
+
if (key.name === "c" && !key.ctrl) {
|
|
26847
26963
|
setConfigOpen(true);
|
|
26848
|
-
|
|
26964
|
+
return;
|
|
26965
|
+
}
|
|
26966
|
+
if (key.name === "?" || key.name === "H" || key.name === "h" && key.shift) {
|
|
26849
26967
|
if (key.name === "?") {
|
|
26850
26968
|
setModal({ type: "help" });
|
|
26851
26969
|
} else {
|
|
26852
26970
|
setModal({ type: "history" });
|
|
26853
26971
|
}
|
|
26854
|
-
|
|
26972
|
+
return;
|
|
26973
|
+
}
|
|
26974
|
+
if (key.name === "n") {
|
|
26855
26975
|
if (!selectedRow)
|
|
26856
26976
|
return;
|
|
26857
26977
|
setCreateModal(true);
|
|
26858
26978
|
setCreateError(undefined);
|
|
26859
|
-
|
|
26979
|
+
return;
|
|
26980
|
+
}
|
|
26981
|
+
if (key.name === "f") {
|
|
26982
|
+
const targets = getSelectedRows();
|
|
26983
|
+
if (targets.length > 0)
|
|
26984
|
+
startFetch(targets);
|
|
26985
|
+
return;
|
|
26986
|
+
}
|
|
26987
|
+
if (key.name === "o") {
|
|
26860
26988
|
const targets = getSelectedRows();
|
|
26861
26989
|
if (targets.length === 0)
|
|
26862
26990
|
return;
|
|
26863
26991
|
if (targets.length > 1) {
|
|
26864
|
-
|
|
26865
|
-
setTimeout(() => setActionMessage(undefined), 3000);
|
|
26992
|
+
flash("Cannot open multiple worktrees");
|
|
26866
26993
|
return;
|
|
26867
26994
|
}
|
|
26868
26995
|
const target = targets[0];
|
|
26869
26996
|
if (!target)
|
|
26870
26997
|
return;
|
|
26871
|
-
|
|
26872
|
-
|
|
26998
|
+
const conflict = findConflict(targets);
|
|
26999
|
+
if (conflict) {
|
|
27000
|
+
flash(conflict);
|
|
27001
|
+
return;
|
|
27002
|
+
}
|
|
27003
|
+
const op = {
|
|
27004
|
+
id: nextOpId.current++,
|
|
27005
|
+
kind: "open",
|
|
27006
|
+
repoNames: [target.repoName],
|
|
27007
|
+
rowPath: target.path,
|
|
27008
|
+
label: target.branch,
|
|
27009
|
+
title: `Open ${target.branch}`,
|
|
27010
|
+
status: "queued",
|
|
27011
|
+
lines: []
|
|
27012
|
+
};
|
|
27013
|
+
setOps((prev) => [...prev, op]);
|
|
27014
|
+
executeOp(op, ["open", target.branch, "--repo", target.repoName], null);
|
|
27015
|
+
return;
|
|
27016
|
+
}
|
|
27017
|
+
if (key.name === "d" || key.name === "D" && key.shift) {
|
|
26873
27018
|
const targets = getSelectedRows();
|
|
26874
27019
|
if (targets.length === 0)
|
|
26875
27020
|
return;
|
|
26876
27021
|
if (targets.some((r) => r.isMainCheckout)) {
|
|
26877
|
-
|
|
26878
|
-
|
|
27022
|
+
flash("Cannot remove main checkout");
|
|
27023
|
+
return;
|
|
27024
|
+
}
|
|
27025
|
+
const conflict = findConflict(targets);
|
|
27026
|
+
if (conflict) {
|
|
27027
|
+
flash(conflict);
|
|
26879
27028
|
return;
|
|
26880
27029
|
}
|
|
26881
27030
|
setModal({ type: "confirm_remove", rows: targets });
|
|
26882
|
-
|
|
27031
|
+
return;
|
|
27032
|
+
}
|
|
27033
|
+
if (key.name === "b" || key.name === "R" || key.name === "r" && key.shift) {
|
|
26883
27034
|
const targets = getSelectedRows();
|
|
26884
27035
|
if (targets.length === 0)
|
|
26885
27036
|
return;
|
|
26886
27037
|
if (targets.some((r) => r.isMainCheckout)) {
|
|
26887
|
-
|
|
26888
|
-
|
|
27038
|
+
flash("Cannot rebase main checkout");
|
|
27039
|
+
return;
|
|
27040
|
+
}
|
|
27041
|
+
const conflict = findConflict(targets);
|
|
27042
|
+
if (conflict) {
|
|
27043
|
+
flash(conflict);
|
|
26889
27044
|
return;
|
|
26890
27045
|
}
|
|
26891
27046
|
setModal({ type: "confirm_rebase", rows: targets });
|
|
26892
|
-
|
|
27047
|
+
return;
|
|
27048
|
+
}
|
|
27049
|
+
if (key.name === "s") {
|
|
26893
27050
|
const targets = getSelectedRows();
|
|
26894
27051
|
if (targets.length === 0)
|
|
26895
27052
|
return;
|
|
26896
27053
|
if (targets.some((r) => r.isMainCheckout)) {
|
|
26897
|
-
|
|
26898
|
-
|
|
27054
|
+
flash("Cannot sync main checkout");
|
|
27055
|
+
return;
|
|
27056
|
+
}
|
|
27057
|
+
const conflict = findConflict(targets);
|
|
27058
|
+
if (conflict) {
|
|
27059
|
+
flash(conflict);
|
|
26899
27060
|
return;
|
|
26900
27061
|
}
|
|
26901
27062
|
setModal({ type: "confirm_sync", rows: targets });
|
|
27063
|
+
return;
|
|
27064
|
+
}
|
|
27065
|
+
if (key.name === "a") {
|
|
27066
|
+
const targets = getSelectedRows();
|
|
27067
|
+
if (targets.length === 0)
|
|
27068
|
+
return;
|
|
27069
|
+
const target = targets[0];
|
|
27070
|
+
if (!target)
|
|
27071
|
+
return;
|
|
27072
|
+
const conflict = findConflict([target]);
|
|
27073
|
+
if (conflict) {
|
|
27074
|
+
flash(conflict);
|
|
27075
|
+
return;
|
|
27076
|
+
}
|
|
27077
|
+
(async () => {
|
|
27078
|
+
const startedAt = Date.now();
|
|
27079
|
+
try {
|
|
27080
|
+
const config2 = await loadConfig();
|
|
27081
|
+
const cmdTemplate = resolveAgentCommand("claude", config2.agents) ?? "claude";
|
|
27082
|
+
const result = await spawnAgentInWorktree(cmdTemplate, target.path, { repoName: target.repoName, branch: target.branch });
|
|
27083
|
+
appendHistory({
|
|
27084
|
+
ts: new Date().toISOString(),
|
|
27085
|
+
source: "terminal",
|
|
27086
|
+
command: "agent",
|
|
27087
|
+
args: ["agent", target.branch, "--repo", target.repoName],
|
|
27088
|
+
durationMs: Date.now() - startedAt,
|
|
27089
|
+
exit: 0
|
|
27090
|
+
});
|
|
27091
|
+
flash(`Agent spawned (${result.mode}${result.session ? ` session ${result.session}` : ""})`, 5000);
|
|
27092
|
+
} catch (e) {
|
|
27093
|
+
appendHistory({
|
|
27094
|
+
ts: new Date().toISOString(),
|
|
27095
|
+
source: "terminal",
|
|
27096
|
+
command: "agent",
|
|
27097
|
+
args: ["agent", target.branch, "--repo", target.repoName],
|
|
27098
|
+
durationMs: Date.now() - startedAt,
|
|
27099
|
+
exit: 1
|
|
27100
|
+
});
|
|
27101
|
+
flash(`Agent failed: ${e.message}`, 5000);
|
|
27102
|
+
}
|
|
27103
|
+
})();
|
|
26902
27104
|
}
|
|
26903
27105
|
});
|
|
26904
27106
|
return /* @__PURE__ */ jsxs10("box", {
|
|
@@ -26912,10 +27114,12 @@ function App({ opts }) {
|
|
|
26912
27114
|
flexGrow: 1,
|
|
26913
27115
|
children: [
|
|
26914
27116
|
/* @__PURE__ */ jsx11(WorktreeTable, {
|
|
26915
|
-
blocks:
|
|
27117
|
+
blocks: displayBlocks,
|
|
26916
27118
|
selectedIndex,
|
|
26917
27119
|
selection,
|
|
26918
|
-
|
|
27120
|
+
frame: spinnerFrame,
|
|
27121
|
+
repoVerbs,
|
|
27122
|
+
rowVerbs
|
|
26919
27123
|
}),
|
|
26920
27124
|
/* @__PURE__ */ jsx11(DetailPane, {
|
|
26921
27125
|
selectedRow
|
|
@@ -26939,11 +27143,11 @@ function App({ opts }) {
|
|
|
26939
27143
|
]
|
|
26940
27144
|
}),
|
|
26941
27145
|
/* @__PURE__ */ jsx11(Footer, {
|
|
26942
|
-
loading,
|
|
27146
|
+
loading: loading || refreshing,
|
|
26943
27147
|
lastRefreshed,
|
|
26944
27148
|
errorCount: warnings.length,
|
|
26945
27149
|
message: actionMessage,
|
|
26946
|
-
busyText:
|
|
27150
|
+
busyText: latestOp ? `${capitalize(VERBS[latestOp.kind])} ${latestOp.label}…${runningOps.length > 1 ? ` (+${runningOps.length - 1})` : ""}` : undefined,
|
|
26947
27151
|
spinnerFrame,
|
|
26948
27152
|
filter: filterText ? { term: filterText, matches: flatRows.length, total: totalRows } : undefined
|
|
26949
27153
|
}),
|
|
@@ -26979,7 +27183,7 @@ function App({ opts }) {
|
|
|
26979
27183
|
message: `Are you sure you want to sync ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`
|
|
26980
27184
|
}),
|
|
26981
27185
|
createModal && /* @__PURE__ */ jsx11(InputModal, {
|
|
26982
|
-
title:
|
|
27186
|
+
title: `New worktree branch in ${selectedRow?.repoName ?? ""}`,
|
|
26983
27187
|
placeholder: "Branch name (empty to cancel)",
|
|
26984
27188
|
errorMessage: createError,
|
|
26985
27189
|
onSubmit: (value) => {
|
|
@@ -26992,10 +27196,16 @@ function App({ opts }) {
|
|
|
26992
27196
|
setCreateError("Invalid branch name");
|
|
26993
27197
|
return;
|
|
26994
27198
|
}
|
|
26995
|
-
|
|
26996
|
-
if (
|
|
26997
|
-
|
|
27199
|
+
const repoName = selectedRow?.repoName;
|
|
27200
|
+
if (!repoName)
|
|
27201
|
+
return;
|
|
27202
|
+
if (busyRepos.has(repoName)) {
|
|
27203
|
+
setCreateError(`${repoName} is busy`);
|
|
27204
|
+
return;
|
|
26998
27205
|
}
|
|
27206
|
+
setCreateModal(false);
|
|
27207
|
+
setCreateError(undefined);
|
|
27208
|
+
startCreate(branch, repoName);
|
|
26999
27209
|
}
|
|
27000
27210
|
}),
|
|
27001
27211
|
configOpen && /* @__PURE__ */ jsx11(ConfigOverlay, {
|
|
@@ -27003,11 +27213,12 @@ function App({ opts }) {
|
|
|
27003
27213
|
onSaved: () => refresh(),
|
|
27004
27214
|
onError: (msg) => setModal({ type: "error", message: msg })
|
|
27005
27215
|
}),
|
|
27006
|
-
|
|
27007
|
-
title:
|
|
27008
|
-
lines:
|
|
27009
|
-
done:
|
|
27010
|
-
exitCode:
|
|
27216
|
+
failedLogs.length > 0 && /* @__PURE__ */ jsx11(ActionLogModal, {
|
|
27217
|
+
title: failedLogs[0].title,
|
|
27218
|
+
lines: failedLogs[0].lines,
|
|
27219
|
+
done: true,
|
|
27220
|
+
exitCode: failedLogs[0].exitCode,
|
|
27221
|
+
remaining: failedLogs.length - 1
|
|
27011
27222
|
})
|
|
27012
27223
|
]
|
|
27013
27224
|
});
|
|
@@ -31194,7 +31405,7 @@ function registerTerminalCommand(program2) {
|
|
|
31194
31405
|
`);
|
|
31195
31406
|
process.stderr.write(` To use the dashboard, run it via Bun:
|
|
31196
31407
|
`);
|
|
31197
|
-
process.stderr.write(` bunx wtx terminal
|
|
31408
|
+
process.stderr.write(` bunx --bun wtx terminal
|
|
31198
31409
|
|
|
31199
31410
|
`);
|
|
31200
31411
|
process.stderr.write(" Note: `wtx ls` provides a fast list view and works everywhere.\n");
|