@ogpoyraz/wtx 0.8.8 → 0.8.10

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +1541 -419
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __returnValue = (v) => v;
4
5
  function __exportSetter(name, newValue) {
@@ -14,6 +15,7 @@ var __export = (target, all) => {
14
15
  });
15
16
  };
16
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
17
19
 
18
20
  // node_modules/zod/v4/core/core.js
19
21
  function $constructor(name, initializer, params) {
@@ -14672,7 +14674,7 @@ function getVersion() {
14672
14674
  return "0.0.0";
14673
14675
  }
14674
14676
  }
14675
- var DepsManagerEnum, DepsStrategyEnum, DepsConfigSchema, AgentConfigSchema, PortsConfigSchema, LEGACY_REPO_KEYS, RepoConfigSchema, ConfigSchema, VERSION;
14677
+ var DepsManagerEnum, DepsStrategyEnum, DepsConfigSchema, AgentConfigSchema, PortsConfigSchema, TuiConfigSchema, LEGACY_REPO_KEYS, RepoConfigSchema, ConfigSchema, VERSION;
14676
14678
  var init_types = __esm(() => {
14677
14679
  init_v4();
14678
14680
  DepsManagerEnum = exports_external.enum(["auto", "npm", "bun", "pnpm", "yarn", "go", "python", "cargo"]);
@@ -14688,6 +14690,10 @@ var init_types = __esm(() => {
14688
14690
  min: exports_external.number().int().min(1024).max(65535).default(4100),
14689
14691
  max: exports_external.number().int().min(1024).max(65535).default(4999)
14690
14692
  }).refine((v) => v.min <= v.max, { message: "ports.min must be less than or equal to ports.max" });
14693
+ TuiConfigSchema = exports_external.object({
14694
+ leftPaneWidthWeight: exports_external.number().int().min(1).max(10).default(3),
14695
+ rightPaneWidthWeight: exports_external.number().int().min(1).max(10).default(7)
14696
+ });
14691
14697
  LEGACY_REPO_KEYS = {
14692
14698
  pr: "check_prs",
14693
14699
  forge: "forge_provider",
@@ -14730,7 +14736,8 @@ var init_types = __esm(() => {
14730
14736
  user: exports_external.string().trim().min(1, { message: "must not be empty if provided" }).nullable().default(null),
14731
14737
  repos: exports_external.record(exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, "Invalid repo key format"), RepoConfigSchema),
14732
14738
  agents: exports_external.record(exports_external.string().regex(/^[a-z][a-z0-9_-]*$/, "Agent names must be lowercase alphanumeric with dashes/underscores"), AgentConfigSchema).optional(),
14733
- ports: PortsConfigSchema.default({ min: 4100, max: 4999 })
14739
+ ports: PortsConfigSchema.default({ min: 4100, max: 4999 }),
14740
+ tui: TuiConfigSchema.default({ leftPaneWidthWeight: 3, rightPaneWidthWeight: 7 })
14734
14741
  });
14735
14742
  VERSION = getVersion();
14736
14743
  });
@@ -25010,80 +25017,6 @@ var init_owner = __esm(() => {
25010
25017
  init_git();
25011
25018
  });
25012
25019
 
25013
- // src/lib/agents.ts
25014
- function resolveAgentCommand(name, configured) {
25015
- return configured?.[name]?.command ?? DEFAULT_AGENTS[name] ?? null;
25016
- }
25017
- function listAvailableAgents(configured) {
25018
- return [...new Set([...Object.keys(DEFAULT_AGENTS), ...Object.keys(configured ?? {})])];
25019
- }
25020
- async function isTmuxAvailable() {
25021
- if (tmuxAvailable !== undefined)
25022
- return tmuxAvailable;
25023
- try {
25024
- await execa("tmux", ["-V"]);
25025
- tmuxAvailable = true;
25026
- } catch {
25027
- tmuxAvailable = false;
25028
- }
25029
- return tmuxAvailable;
25030
- }
25031
- function tmuxSessionName(repoName, branch) {
25032
- const sanitized = `${repoName}-${branch}`.replace(/[\/.:]/g, "-");
25033
- return `wtx-${sanitized}`.substring(0, 60);
25034
- }
25035
- function buildTmuxArgs(session, wtPath, cmd) {
25036
- return ["new-session", "-d", "-s", session, "-c", wtPath, cmd];
25037
- }
25038
- async function spawnAgentInWorktree(commandTemplate, wtPath, opts = {}) {
25039
- const cmd = buildAgentCommand(commandTemplate, wtPath, opts.prompt, {
25040
- branch: opts.branch,
25041
- repo: opts.repoName
25042
- });
25043
- const session = opts.repoName && opts.branch ? tmuxSessionName(opts.repoName, opts.branch) : undefined;
25044
- if (opts.dryRun) {
25045
- if (session && await isTmuxAvailable()) {
25046
- return { mode: "tmux", session };
25047
- }
25048
- return { mode: "direct" };
25049
- }
25050
- if (session && await isTmuxAvailable()) {
25051
- try {
25052
- const args = buildTmuxArgs(session, wtPath, cmd);
25053
- await execa("tmux", args, { stdio: "inherit" });
25054
- return { mode: "tmux", session };
25055
- } catch {}
25056
- }
25057
- await execa(cmd, { shell: true, cwd: wtPath, stdio: "inherit" });
25058
- return { mode: "direct" };
25059
- }
25060
- function shellQuote(value) {
25061
- return `'${value.replaceAll("'", `'''`)}'`;
25062
- }
25063
- function buildAgentCommand(commandTemplate, wtPath, prompt, vars) {
25064
- let cmd = commandTemplate.replaceAll("{wt}", shellQuote(wtPath));
25065
- if (vars?.branch) {
25066
- cmd = cmd.replaceAll("{branch}", shellQuote(vars.branch));
25067
- }
25068
- if (vars?.repo) {
25069
- cmd = cmd.replaceAll("{repo}", shellQuote(vars.repo));
25070
- }
25071
- if (prompt) {
25072
- cmd += ` ${shellQuote(prompt)}`;
25073
- }
25074
- return cmd;
25075
- }
25076
- var DEFAULT_AGENTS, tmuxAvailable = undefined;
25077
- var init_agents = __esm(() => {
25078
- init_execa();
25079
- DEFAULT_AGENTS = {
25080
- claude: "claude",
25081
- codex: "codex",
25082
- opencode: "opencode",
25083
- cursor: "cursor"
25084
- };
25085
- });
25086
-
25087
25020
  // src/lib/forge/types.ts
25088
25021
  function displayStateRank(state) {
25089
25022
  return DISPLAY_PRIORITY.indexOf(state);
@@ -25169,6 +25102,18 @@ class Semaphore {
25169
25102
  }
25170
25103
  }
25171
25104
 
25105
+ // src/tui/env.ts
25106
+ var init_env = __esm(() => {
25107
+ process.env.RUST_LOG ??= "fatal";
25108
+ process.env.ZIG_LOG ??= "fatal";
25109
+ process.env.PAGE_LIST_LOG ??= "fatal";
25110
+ process.env.OPENTUI_LOG ??= "fatal";
25111
+ process.env.LOG_LEVEL ??= "fatal";
25112
+ process.env.ZIG_LOG_LEVEL ??= "fatal";
25113
+ process.env.OPENTUI_LOG_LEVEL ??= "fatal";
25114
+ process.env.BUN_DEBUG ??= "0";
25115
+ });
25116
+
25172
25117
  // src/tui/data.ts
25173
25118
  async function fetchWorktreeData(opts, scope) {
25174
25119
  const warnings = [];
@@ -25666,6 +25611,39 @@ async function copyTextToClipboard(renderer, text) {
25666
25611
  }
25667
25612
  return viaSystem || viaTerminal;
25668
25613
  }
25614
+ function pasteCandidatesFor(platform2, env2 = process.env) {
25615
+ if (platform2 === "darwin")
25616
+ return [{ cmd: "pbpaste", args: [] }];
25617
+ if (platform2 === "win32")
25618
+ return [{ cmd: "powershell.exe", args: ["-command", "Get-Clipboard"] }];
25619
+ const candidates = [];
25620
+ if (env2.WAYLAND_DISPLAY)
25621
+ candidates.push({ cmd: "wl-paste", args: ["--no-newline"] });
25622
+ if (env2.DISPLAY) {
25623
+ candidates.push({ cmd: "xclip", args: ["-selection", "clipboard", "-o"] });
25624
+ candidates.push({ cmd: "xsel", args: ["--clipboard", "--output"] });
25625
+ }
25626
+ candidates.push({ cmd: "wl-paste", args: ["--no-newline"] });
25627
+ candidates.push({ cmd: "xclip", args: ["-selection", "clipboard", "-o"] });
25628
+ candidates.push({ cmd: "xsel", args: ["--clipboard", "--output"] });
25629
+ return candidates;
25630
+ }
25631
+ async function readTextFromClipboard() {
25632
+ for (const candidate of pasteCandidatesFor(process.platform)) {
25633
+ try {
25634
+ const proc = Bun.spawn([candidate.cmd, ...candidate.args], {
25635
+ stdin: "ignore",
25636
+ stdout: "pipe",
25637
+ stderr: "ignore"
25638
+ });
25639
+ const text = await new Response(proc.stdout).text();
25640
+ const code = await proc.exited;
25641
+ if (code === 0 && text)
25642
+ return text;
25643
+ } catch {}
25644
+ }
25645
+ return null;
25646
+ }
25669
25647
  function browserCommandFor(platform2, url2) {
25670
25648
  if (!/^https?:\/\//.test(url2))
25671
25649
  return null;
@@ -25935,28 +25913,407 @@ var init_WorktreeTable = __esm(() => {
25935
25913
  init_use_tap();
25936
25914
  });
25937
25915
 
25938
- // src/tui/components/DetailPane.tsx
25939
- import { jsx as jsx2, jsxs as jsxs2, Fragment } from "@opentui/react/jsx-runtime";
25940
- function DetailPane({ selectedRow }) {
25916
+ // src/tui/tabs/TabBar.tsx
25917
+ import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
25918
+ function TabItem({
25919
+ id,
25920
+ label,
25921
+ closable,
25922
+ active,
25923
+ onSelect,
25924
+ onClose
25925
+ }) {
25926
+ const tap = useTapHandler(() => onSelect(id));
25927
+ const closeTap = useTapHandler((e) => {
25928
+ e.stopPropagation();
25929
+ onClose?.(id);
25930
+ });
25931
+ return /* @__PURE__ */ jsxs2("box", {
25932
+ flexDirection: "row",
25933
+ paddingRight: 1,
25934
+ ...tap,
25935
+ children: [
25936
+ /* @__PURE__ */ jsx2("text", {
25937
+ fg: active ? tokens.accent : tokens.dim,
25938
+ attributes: active ? 1 : 0,
25939
+ children: label
25940
+ }),
25941
+ closable ? /* @__PURE__ */ jsx2("box", {
25942
+ ...closeTap,
25943
+ style: { marginLeft: 1 },
25944
+ children: /* @__PURE__ */ jsx2("text", {
25945
+ fg: active ? tokens.accent : tokens.dim,
25946
+ children: "✕"
25947
+ })
25948
+ }) : null,
25949
+ /* @__PURE__ */ jsx2("text", {
25950
+ children: " "
25951
+ })
25952
+ ]
25953
+ });
25954
+ }
25955
+ function TabBar({ tabs, activeId, onSelect, onClose }) {
25956
+ return /* @__PURE__ */ jsx2("box", {
25957
+ flexDirection: "row",
25958
+ width: "100%",
25959
+ alignItems: "center",
25960
+ paddingLeft: 1,
25961
+ children: tabs.map((tab) => /* @__PURE__ */ jsx2(TabItem, {
25962
+ id: tab.id,
25963
+ label: tab.label,
25964
+ closable: tab.closable,
25965
+ active: tab.id === activeId,
25966
+ onSelect,
25967
+ onClose
25968
+ }, tab.id))
25969
+ });
25970
+ }
25971
+ var init_TabBar = __esm(() => {
25972
+ init_theme();
25973
+ init_use_tap();
25974
+ });
25975
+
25976
+ // src/tui/components/TerminalView.tsx
25977
+ import { useState as useState2, useEffect as useEffect3, useRef as useRef4, useCallback as useCallback3 } from "react";
25978
+ import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
25979
+ function TerminalView({ session, focused, onFocus, onSend, onResize, registerListener, unregisterListener }) {
25980
+ const [draft, setDraft] = useState2("");
25981
+ const focusTap = useTapHandler(() => onFocus());
25982
+ const termRef = useRef4(null);
25983
+ const pipeScrollRef = useRef4(null);
25984
+ const handlePtyScroll = useCallback3((e) => {
25985
+ const ev = e;
25986
+ const dir = ev.scroll?.direction;
25987
+ if (dir !== "up" && dir !== "down")
25988
+ return;
25989
+ const term = termRef.current;
25990
+ if (term?.handle && term?.lib) {
25991
+ try {
25992
+ term.lib.embeddedTerminalScroll(term.handle, dir === "up" ? -8 : 8);
25993
+ term.requestRender?.();
25994
+ ev.preventDefault?.();
25995
+ ev.stopPropagation?.();
25996
+ } catch {}
25997
+ }
25998
+ }, []);
25999
+ const handlePipeScroll = useCallback3((e) => {
26000
+ const ev = e;
26001
+ const dir = ev.scroll?.direction;
26002
+ if (dir !== "up" && dir !== "down")
26003
+ return;
26004
+ try {
26005
+ pipeScrollRef.current?.scrollBy(dir === "up" ? -4 : 4);
26006
+ ev.preventDefault?.();
26007
+ ev.stopPropagation?.();
26008
+ } catch {}
26009
+ }, []);
26010
+ const handleData = useCallback3((data, source) => {
26011
+ if (source !== "input")
26012
+ return;
26013
+ if (session.usePty && focused)
26014
+ return;
26015
+ onSend(data);
26016
+ }, [onSend, focused, session.id, session.usePty]);
26017
+ const handleResize = useCallback3((cols, rows) => {
26018
+ onResize?.(cols, rows);
26019
+ }, [onResize]);
26020
+ useEffect3(() => {
26021
+ if (!session.usePty || !session.terminal)
26022
+ return;
26023
+ if (!termRef.current || !registerListener || !unregisterListener)
26024
+ return;
26025
+ const write = (data) => {
26026
+ try {
26027
+ termRef.current?.write(data);
26028
+ } catch {}
26029
+ };
26030
+ registerListener(session.id, write);
26031
+ try {
26032
+ termRef.current?.invalidate?.();
26033
+ } catch {}
26034
+ return () => unregisterListener(session.id);
26035
+ }, [session.id, session.usePty, session.terminal, registerListener, unregisterListener]);
26036
+ useEffect3(() => {
26037
+ if (!session.usePty)
26038
+ return;
26039
+ if (!termRef.current)
26040
+ return;
26041
+ try {
26042
+ if (focused)
26043
+ termRef.current.focus?.();
26044
+ else
26045
+ termRef.current.blur?.();
26046
+ } catch {}
26047
+ }, [focused, session.usePty]);
26048
+ useEffect3(() => {
26049
+ if (!session.usePty || !session.terminal)
26050
+ return;
26051
+ if (!termRef.current)
26052
+ return;
26053
+ try {
26054
+ termRef.current.invalidate?.();
26055
+ } catch {}
26056
+ }, [session.cols, session.rows, session.usePty]);
26057
+ useEffect3(() => {
26058
+ if (!session.usePty || !termRef.current)
26059
+ return;
26060
+ const term = termRef.current;
26061
+ const orig = term.forwardMouse?.bind(term);
26062
+ if (!orig)
26063
+ return;
26064
+ const patched = (event, action) => {
26065
+ const ev = event;
26066
+ if (ev.type === "scroll") {
26067
+ const dir = ev.scroll?.direction;
26068
+ const shift = ev.modifiers?.shift;
26069
+ if (dir === "up" || dir === "down") {
26070
+ const shouldScrollOuter = shift === true;
26071
+ if (!shouldScrollOuter) {
26072
+ return orig(event, action);
26073
+ }
26074
+ try {
26075
+ if (term.handle && term.lib) {
26076
+ term.lib.embeddedTerminalScroll(term.handle, dir === "up" ? -12 : 12);
26077
+ term.requestRender?.();
26078
+ }
26079
+ ev.preventDefault?.();
26080
+ ev.stopPropagation?.();
26081
+ return;
26082
+ } catch {}
26083
+ }
26084
+ }
26085
+ return orig(event, action);
26086
+ };
26087
+ term.forwardMouse = patched;
26088
+ return () => {
26089
+ term.forwardMouse = orig;
26090
+ };
26091
+ }, [session.id, session.usePty, session.terminal]);
26092
+ if (session.usePty && session.terminal) {
26093
+ return /* @__PURE__ */ jsxs3("box", {
26094
+ flexDirection: "column",
26095
+ flexGrow: 1,
26096
+ width: "100%",
26097
+ height: "100%",
26098
+ ...focusTap,
26099
+ onMouseScroll: handlePtyScroll,
26100
+ children: [
26101
+ /* @__PURE__ */ jsx3("box", {
26102
+ flexGrow: 1,
26103
+ width: "100%",
26104
+ height: "100%",
26105
+ flexDirection: "column",
26106
+ padding: 1,
26107
+ onMouseScroll: handlePtyScroll,
26108
+ children: /* @__PURE__ */ jsx3("embedded-terminal", {
26109
+ ref: termRef,
26110
+ width: "100%",
26111
+ height: "100%",
26112
+ cols: session.cols,
26113
+ rows: session.rows,
26114
+ maxScrollback: 1e4,
26115
+ onData: handleData,
26116
+ onTerminalResize: handleResize,
26117
+ selectable: true,
26118
+ focused
26119
+ })
26120
+ }),
26121
+ session.exited !== null && /* @__PURE__ */ jsxs3("text", {
26122
+ fg: tokens.warning,
26123
+ children: [
26124
+ "— exited with code ",
26125
+ session.exited,
26126
+ " — press t for new session"
26127
+ ]
26128
+ }),
26129
+ !focused && /* @__PURE__ */ jsxs3("text", {
26130
+ fg: tokens.dim,
26131
+ children: [
26132
+ "Click to focus · Ctrl+G or click table to unfocus · ",
26133
+ session.label,
26134
+ " PTY · scroll wheel to view history"
26135
+ ]
26136
+ }),
26137
+ focused && /* @__PURE__ */ jsx3("text", {
26138
+ fg: tokens.accent,
26139
+ children: "Focused PTY — all keys go to shell · Ctrl+G to unfocus · scroll wheel to view history"
26140
+ })
26141
+ ]
26142
+ });
26143
+ }
26144
+ const lines = session.lines;
26145
+ return /* @__PURE__ */ jsxs3("box", {
26146
+ flexDirection: "column",
26147
+ flexGrow: 1,
26148
+ width: "100%",
26149
+ height: "100%",
26150
+ ...focusTap,
26151
+ onMouseScroll: handlePipeScroll,
26152
+ children: [
26153
+ /* @__PURE__ */ jsxs3("scrollbox", {
26154
+ ref: pipeScrollRef,
26155
+ flexGrow: 1,
26156
+ width: "100%",
26157
+ border: false,
26158
+ focused: false,
26159
+ stickyScroll: false,
26160
+ style: { marginBottom: 1 },
26161
+ onMouseScroll: handlePipeScroll,
26162
+ children: [
26163
+ lines.length === 0 ? /* @__PURE__ */ jsx3("text", {
26164
+ fg: tokens.dim,
26165
+ children: "No output yet — type a command below."
26166
+ }) : lines.slice(-500).map((line, idx) => /* @__PURE__ */ jsx3("text", {
26167
+ fg: session.exited !== null ? tokens.dim : tokens.fg,
26168
+ children: line || " "
26169
+ }, idx)),
26170
+ session.exited !== null && /* @__PURE__ */ jsxs3("text", {
26171
+ fg: tokens.warning,
26172
+ children: [
26173
+ "— exited with code ",
26174
+ session.exited,
26175
+ " —"
26176
+ ]
26177
+ })
26178
+ ]
26179
+ }),
26180
+ /* @__PURE__ */ jsxs3("box", {
26181
+ flexDirection: "row",
26182
+ gap: 1,
26183
+ width: "100%",
26184
+ children: [
26185
+ /* @__PURE__ */ jsx3("text", {
26186
+ fg: tokens.dim,
26187
+ children: "$"
26188
+ }),
26189
+ /* @__PURE__ */ jsx3("box", {
26190
+ flexGrow: 1,
26191
+ children: /* @__PURE__ */ jsx3("input", {
26192
+ focused,
26193
+ value: draft,
26194
+ placeholder: focused ? "Type command, Enter to send, Esc to unfocus" : "Click to focus terminal",
26195
+ onInput: (v) => setDraft(v),
26196
+ onSubmit: () => {
26197
+ if (!draft)
26198
+ return;
26199
+ onSend(draft + `
26200
+ `);
26201
+ setDraft("");
26202
+ }
26203
+ })
26204
+ })
26205
+ ]
26206
+ }),
26207
+ !focused && /* @__PURE__ */ jsx3("text", {
26208
+ fg: tokens.dim,
26209
+ children: "Click terminal or press 't' to focus · Ctrl+G or click table to unfocus"
26210
+ }),
26211
+ focused && /* @__PURE__ */ jsx3("text", {
26212
+ fg: tokens.accent,
26213
+ children: "Focused — typing goes to shell · Ctrl+G to unfocus · max 5 per worktree persist across switches"
26214
+ })
26215
+ ]
26216
+ });
26217
+ }
26218
+ var init_TerminalView = __esm(() => {
26219
+ init_theme();
26220
+ init_use_tap();
26221
+ });
26222
+
26223
+ // src/tui/tabs/TabPane.tsx
26224
+ import { jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
26225
+ function TabPane({
26226
+ selectedRow,
26227
+ tabs,
26228
+ activeId,
26229
+ focused,
26230
+ canAdd,
26231
+ onSelect,
26232
+ onAdd,
26233
+ onClose,
26234
+ allSessionsFlat,
26235
+ terminalFocused,
26236
+ activeTabId,
26237
+ terminalSessions
26238
+ }) {
26239
+ const borderColor = focused ? tokens.accent : tokens.border;
26240
+ return /* @__PURE__ */ jsxs4("box", {
26241
+ flexDirection: "column",
26242
+ flexGrow: 1,
26243
+ width: "100%",
26244
+ height: "100%",
26245
+ border: true,
26246
+ borderColor,
26247
+ focusedBorderColor: borderColor,
26248
+ focusable: false,
26249
+ id: "detail-pane",
26250
+ children: [
26251
+ /* @__PURE__ */ jsx4(TabBar, {
26252
+ tabs,
26253
+ activeId,
26254
+ canAdd,
26255
+ onSelect,
26256
+ onAdd,
26257
+ onClose
26258
+ }),
26259
+ /* @__PURE__ */ jsxs4("box", {
26260
+ flexGrow: 1,
26261
+ width: "100%",
26262
+ flexDirection: "column",
26263
+ children: [
26264
+ tabs.map((t) => /* @__PURE__ */ jsx4("box", {
26265
+ flexGrow: 1,
26266
+ width: "100%",
26267
+ flexDirection: "column",
26268
+ visible: t.id === activeId && t.id === "details",
26269
+ children: t.render({ worktree: selectedRow, isActive: t.id === activeId })
26270
+ }, t.id)),
26271
+ allSessionsFlat?.map((s) => /* @__PURE__ */ jsx4("box", {
26272
+ flexGrow: 1,
26273
+ width: "100%",
26274
+ height: "100%",
26275
+ flexDirection: "column",
26276
+ visible: activeTabId === s.id,
26277
+ children: /* @__PURE__ */ jsx4(TerminalView, {
26278
+ session: s,
26279
+ focused: !!(terminalFocused && activeTabId === s.id),
26280
+ onFocus: () => {},
26281
+ onSend: (data) => terminalSessions?.sendInput(s.repoName, s.branch, s.worktreePath, s.id, data),
26282
+ onResize: (cols, rows) => terminalSessions?.resizeSession(s.repoName, s.branch, s.worktreePath, s.id, cols, rows),
26283
+ registerListener: terminalSessions?.registerListener,
26284
+ unregisterListener: terminalSessions?.unregisterListener
26285
+ })
26286
+ }, s.id)),
26287
+ tabs.length === 0 && (!allSessionsFlat || allSessionsFlat.length === 0) && /* @__PURE__ */ jsx4("text", {
26288
+ fg: tokens.dim,
26289
+ children: "No tab"
26290
+ })
26291
+ ]
26292
+ })
26293
+ ]
26294
+ });
26295
+ }
26296
+ var init_TabPane = __esm(() => {
26297
+ init_theme();
26298
+ init_TabBar();
26299
+ init_TerminalView();
26300
+ });
26301
+
26302
+ // src/tui/components/DetailsTab.tsx
26303
+ import { jsx as jsx5, jsxs as jsxs5, Fragment } from "@opentui/react/jsx-runtime";
26304
+ function DetailsContent({ selectedRow }) {
25941
26305
  const prTap = useTapHandler(() => {
25942
26306
  if (selectedRow?.prUrl)
25943
26307
  openInBrowser(selectedRow.prUrl);
25944
26308
  });
25945
26309
  if (!selectedRow) {
25946
- return /* @__PURE__ */ jsx2("box", {
25947
- id: "detail-pane",
26310
+ return /* @__PURE__ */ jsx5("box", {
25948
26311
  flexGrow: 1,
25949
26312
  width: "100%",
25950
26313
  height: "100%",
25951
- border: true,
25952
- borderColor: tokens.border,
25953
- focusedBorderColor: tokens.border,
25954
- focusable: false,
25955
- title: "Details",
25956
- padding: 1,
25957
26314
  justifyContent: "center",
25958
26315
  alignItems: "center",
25959
- children: /* @__PURE__ */ jsx2("text", {
26316
+ children: /* @__PURE__ */ jsx5("text", {
25960
26317
  fg: tokens.dim,
25961
26318
  children: "No worktree selected"
25962
26319
  })
@@ -25984,26 +26341,20 @@ function DetailPane({ selectedRow }) {
25984
26341
  baseChanged
25985
26342
  } = selectedRow;
25986
26343
  const aheadBehindStr = ahead !== null && behind !== null ? `${ahead} ahead, ${behind} behind` : "unknown";
25987
- return /* @__PURE__ */ jsxs2("scrollbox", {
25988
- id: "detail-pane",
26344
+ return /* @__PURE__ */ jsxs5("scrollbox", {
25989
26345
  flexGrow: 1,
25990
26346
  width: "100%",
25991
26347
  height: "100%",
25992
- border: true,
25993
- borderColor: tokens.border,
25994
- focusedBorderColor: tokens.border,
25995
- focusable: false,
25996
- title: "Details",
25997
- padding: 1,
25998
26348
  focused: false,
26349
+ padding: 1,
25999
26350
  children: [
26000
- /* @__PURE__ */ jsxs2("text", {
26351
+ /* @__PURE__ */ jsxs5("text", {
26001
26352
  children: [
26002
- /* @__PURE__ */ jsx2("span", {
26353
+ /* @__PURE__ */ jsx5("span", {
26003
26354
  fg: tokens.dim,
26004
26355
  children: "Repo:"
26005
26356
  }),
26006
- /* @__PURE__ */ jsxs2("span", {
26357
+ /* @__PURE__ */ jsxs5("span", {
26007
26358
  fg: tokens.fg,
26008
26359
  children: [
26009
26360
  " ",
@@ -26012,13 +26363,13 @@ function DetailPane({ selectedRow }) {
26012
26363
  })
26013
26364
  ]
26014
26365
  }),
26015
- /* @__PURE__ */ jsxs2("text", {
26366
+ /* @__PURE__ */ jsxs5("text", {
26016
26367
  children: [
26017
- /* @__PURE__ */ jsx2("span", {
26368
+ /* @__PURE__ */ jsx5("span", {
26018
26369
  fg: tokens.dim,
26019
26370
  children: "Branch:"
26020
26371
  }),
26021
- /* @__PURE__ */ jsxs2("span", {
26372
+ /* @__PURE__ */ jsxs5("span", {
26022
26373
  fg: tokens.fg,
26023
26374
  children: [
26024
26375
  " ",
@@ -26029,13 +26380,13 @@ function DetailPane({ selectedRow }) {
26029
26380
  })
26030
26381
  ]
26031
26382
  }),
26032
- /* @__PURE__ */ jsxs2("text", {
26383
+ /* @__PURE__ */ jsxs5("text", {
26033
26384
  children: [
26034
- /* @__PURE__ */ jsx2("span", {
26385
+ /* @__PURE__ */ jsx5("span", {
26035
26386
  fg: tokens.dim,
26036
26387
  children: "Path:"
26037
26388
  }),
26038
- /* @__PURE__ */ jsxs2("span", {
26389
+ /* @__PURE__ */ jsxs5("span", {
26039
26390
  fg: tokens.fg,
26040
26391
  children: [
26041
26392
  " ",
@@ -26044,13 +26395,13 @@ function DetailPane({ selectedRow }) {
26044
26395
  })
26045
26396
  ]
26046
26397
  }),
26047
- /* @__PURE__ */ jsxs2("text", {
26398
+ /* @__PURE__ */ jsxs5("text", {
26048
26399
  children: [
26049
- /* @__PURE__ */ jsx2("span", {
26400
+ /* @__PURE__ */ jsx5("span", {
26050
26401
  fg: tokens.dim,
26051
26402
  children: "Commit:"
26052
26403
  }),
26053
- /* @__PURE__ */ jsxs2("span", {
26404
+ /* @__PURE__ */ jsxs5("span", {
26054
26405
  fg: tokens.fg,
26055
26406
  children: [
26056
26407
  " ",
@@ -26059,13 +26410,13 @@ function DetailPane({ selectedRow }) {
26059
26410
  })
26060
26411
  ]
26061
26412
  }),
26062
- owner && /* @__PURE__ */ jsxs2("text", {
26413
+ owner && /* @__PURE__ */ jsxs5("text", {
26063
26414
  children: [
26064
- /* @__PURE__ */ jsx2("span", {
26415
+ /* @__PURE__ */ jsx5("span", {
26065
26416
  fg: tokens.dim,
26066
26417
  children: "Owner:"
26067
26418
  }),
26068
- /* @__PURE__ */ jsxs2("span", {
26419
+ /* @__PURE__ */ jsxs5("span", {
26069
26420
  fg: tokens.fg,
26070
26421
  children: [
26071
26422
  " ",
@@ -26074,13 +26425,13 @@ function DetailPane({ selectedRow }) {
26074
26425
  })
26075
26426
  ]
26076
26427
  }),
26077
- /* @__PURE__ */ jsxs2("text", {
26428
+ /* @__PURE__ */ jsxs5("text", {
26078
26429
  children: [
26079
- /* @__PURE__ */ jsx2("span", {
26430
+ /* @__PURE__ */ jsx5("span", {
26080
26431
  fg: tokens.dim,
26081
26432
  children: "Status:"
26082
26433
  }),
26083
- /* @__PURE__ */ jsxs2("span", {
26434
+ /* @__PURE__ */ jsxs5("span", {
26084
26435
  fg: tokens.fg,
26085
26436
  children: [
26086
26437
  " ",
@@ -26089,13 +26440,13 @@ function DetailPane({ selectedRow }) {
26089
26440
  })
26090
26441
  ]
26091
26442
  }),
26092
- base2 && /* @__PURE__ */ jsxs2("text", {
26443
+ base2 && /* @__PURE__ */ jsxs5("text", {
26093
26444
  children: [
26094
- /* @__PURE__ */ jsx2("span", {
26445
+ /* @__PURE__ */ jsx5("span", {
26095
26446
  fg: tokens.dim,
26096
26447
  children: "Base:"
26097
26448
  }),
26098
- /* @__PURE__ */ jsxs2("span", {
26449
+ /* @__PURE__ */ jsxs5("span", {
26099
26450
  fg: tokens.accent,
26100
26451
  children: [
26101
26452
  " ",
@@ -26104,26 +26455,26 @@ function DetailPane({ selectedRow }) {
26104
26455
  })
26105
26456
  ]
26106
26457
  }),
26107
- baseChanged && /* @__PURE__ */ jsxs2("text", {
26458
+ baseChanged && /* @__PURE__ */ jsxs5("text", {
26108
26459
  children: [
26109
- /* @__PURE__ */ jsx2("span", {
26460
+ /* @__PURE__ */ jsx5("span", {
26110
26461
  fg: tokens.dim,
26111
26462
  children: "Base state:"
26112
26463
  }),
26113
- /* @__PURE__ */ jsx2("span", {
26464
+ /* @__PURE__ */ jsx5("span", {
26114
26465
  fg: tokens.warning,
26115
26466
  children: " moved since recorded"
26116
26467
  })
26117
26468
  ]
26118
26469
  }),
26119
- /* @__PURE__ */ jsxs2("text", {
26470
+ /* @__PURE__ */ jsxs5("text", {
26120
26471
  style: { marginTop: 1 },
26121
26472
  children: [
26122
- /* @__PURE__ */ jsx2("span", {
26473
+ /* @__PURE__ */ jsx5("span", {
26123
26474
  fg: tokens.dim,
26124
26475
  children: base2 ? "vs base:" : "vs main:"
26125
26476
  }),
26126
- /* @__PURE__ */ jsxs2("span", {
26477
+ /* @__PURE__ */ jsxs5("span", {
26127
26478
  fg: tokens.fg,
26128
26479
  children: [
26129
26480
  " ",
@@ -26132,13 +26483,13 @@ function DetailPane({ selectedRow }) {
26132
26483
  })
26133
26484
  ]
26134
26485
  }),
26135
- /* @__PURE__ */ jsxs2("text", {
26486
+ /* @__PURE__ */ jsxs5("text", {
26136
26487
  children: [
26137
- /* @__PURE__ */ jsx2("span", {
26488
+ /* @__PURE__ */ jsx5("span", {
26138
26489
  fg: tokens.dim,
26139
26490
  children: "Deps:"
26140
26491
  }),
26141
- /* @__PURE__ */ jsxs2("span", {
26492
+ /* @__PURE__ */ jsxs5("span", {
26142
26493
  fg: tokens.fg,
26143
26494
  children: [
26144
26495
  " ",
@@ -26147,13 +26498,13 @@ function DetailPane({ selectedRow }) {
26147
26498
  })
26148
26499
  ]
26149
26500
  }),
26150
- rebaseStatus && /* @__PURE__ */ jsxs2("text", {
26501
+ rebaseStatus && /* @__PURE__ */ jsxs5("text", {
26151
26502
  children: [
26152
- /* @__PURE__ */ jsx2("span", {
26503
+ /* @__PURE__ */ jsx5("span", {
26153
26504
  fg: tokens.dim,
26154
26505
  children: "Rebase:"
26155
26506
  }),
26156
- /* @__PURE__ */ jsxs2("span", {
26507
+ /* @__PURE__ */ jsxs5("span", {
26157
26508
  fg: tokens.error,
26158
26509
  children: [
26159
26510
  " ",
@@ -26162,12 +26513,12 @@ function DetailPane({ selectedRow }) {
26162
26513
  })
26163
26514
  ]
26164
26515
  }),
26165
- prNumber !== null && /* @__PURE__ */ jsxs2(Fragment, {
26516
+ prNumber !== null && /* @__PURE__ */ jsxs5(Fragment, {
26166
26517
  children: [
26167
- /* @__PURE__ */ jsxs2("text", {
26518
+ /* @__PURE__ */ jsxs5("text", {
26168
26519
  style: { marginTop: 1 },
26169
26520
  children: [
26170
- /* @__PURE__ */ jsxs2("span", {
26521
+ /* @__PURE__ */ jsxs5("span", {
26171
26522
  fg: tokens.accent,
26172
26523
  children: [
26173
26524
  "PR #",
@@ -26175,7 +26526,7 @@ function DetailPane({ selectedRow }) {
26175
26526
  ":"
26176
26527
  ]
26177
26528
  }),
26178
- /* @__PURE__ */ jsxs2("span", {
26529
+ /* @__PURE__ */ jsxs5("span", {
26179
26530
  fg: tokens.dim,
26180
26531
  children: [
26181
26532
  " ",
@@ -26184,13 +26535,13 @@ function DetailPane({ selectedRow }) {
26184
26535
  })
26185
26536
  ]
26186
26537
  }),
26187
- prChecks && /* @__PURE__ */ jsxs2("text", {
26538
+ prChecks && /* @__PURE__ */ jsxs5("text", {
26188
26539
  children: [
26189
- /* @__PURE__ */ jsx2("span", {
26540
+ /* @__PURE__ */ jsx5("span", {
26190
26541
  fg: tokens.dim,
26191
26542
  children: "Checks:"
26192
26543
  }),
26193
- /* @__PURE__ */ jsxs2("span", {
26544
+ /* @__PURE__ */ jsxs5("span", {
26194
26545
  fg: tokens.fg,
26195
26546
  children: [
26196
26547
  " ",
@@ -26199,20 +26550,20 @@ function DetailPane({ selectedRow }) {
26199
26550
  })
26200
26551
  ]
26201
26552
  }),
26202
- prUrl && /* @__PURE__ */ jsx2("box", {
26553
+ prUrl && /* @__PURE__ */ jsx5("box", {
26203
26554
  ...prTap,
26204
- children: /* @__PURE__ */ jsxs2("text", {
26555
+ children: /* @__PURE__ */ jsxs5("text", {
26205
26556
  selectable: false,
26206
26557
  children: [
26207
- /* @__PURE__ */ jsx2("span", {
26558
+ /* @__PURE__ */ jsx5("span", {
26208
26559
  fg: tokens.dim,
26209
26560
  children: "URL:"
26210
26561
  }),
26211
- /* @__PURE__ */ jsx2("span", {
26562
+ /* @__PURE__ */ jsx5("span", {
26212
26563
  fg: tokens.accent,
26213
26564
  children: ` ${prUrl}`
26214
26565
  }),
26215
- /* @__PURE__ */ jsx2("span", {
26566
+ /* @__PURE__ */ jsx5("span", {
26216
26567
  fg: tokens.dim,
26217
26568
  children: " ↗ click"
26218
26569
  })
@@ -26221,11 +26572,11 @@ function DetailPane({ selectedRow }) {
26221
26572
  })
26222
26573
  ]
26223
26574
  }),
26224
- dirtyFiles.length > 0 && /* @__PURE__ */ jsxs2("box", {
26575
+ dirtyFiles.length > 0 && /* @__PURE__ */ jsxs5("box", {
26225
26576
  flexDirection: "column",
26226
26577
  style: { marginTop: 1 },
26227
26578
  children: [
26228
- /* @__PURE__ */ jsxs2("text", {
26579
+ /* @__PURE__ */ jsxs5("text", {
26229
26580
  fg: tokens.warning,
26230
26581
  children: [
26231
26582
  "Dirty Files (",
@@ -26233,14 +26584,14 @@ function DetailPane({ selectedRow }) {
26233
26584
  "):"
26234
26585
  ]
26235
26586
  }),
26236
- dirtyFiles.slice(0, 20).map((file2, idx) => /* @__PURE__ */ jsxs2("text", {
26587
+ dirtyFiles.slice(0, 20).map((file2, idx) => /* @__PURE__ */ jsxs5("text", {
26237
26588
  fg: tokens.warning,
26238
26589
  children: [
26239
26590
  " ",
26240
26591
  file2
26241
26592
  ]
26242
26593
  }, idx)),
26243
- dirtyFiles.length > 20 && /* @__PURE__ */ jsxs2("text", {
26594
+ dirtyFiles.length > 20 && /* @__PURE__ */ jsxs5("text", {
26244
26595
  fg: tokens.warning,
26245
26596
  children: [
26246
26597
  " ...and ",
@@ -26250,17 +26601,17 @@ function DetailPane({ selectedRow }) {
26250
26601
  })
26251
26602
  ]
26252
26603
  }),
26253
- /* @__PURE__ */ jsxs2("box", {
26604
+ /* @__PURE__ */ jsxs5("box", {
26254
26605
  flexDirection: "column",
26255
26606
  style: { marginTop: 1 },
26256
26607
  children: [
26257
- /* @__PURE__ */ jsx2("text", {
26608
+ /* @__PURE__ */ jsx5("text", {
26258
26609
  fg: tokens.dim,
26259
26610
  children: "Actions:"
26260
26611
  }),
26261
- !isMainCheckout && /* @__PURE__ */ jsxs2(Fragment, {
26612
+ !isMainCheckout && /* @__PURE__ */ jsxs5(Fragment, {
26262
26613
  children: [
26263
- /* @__PURE__ */ jsxs2("text", {
26614
+ /* @__PURE__ */ jsxs5("text", {
26264
26615
  fg: tokens.dim,
26265
26616
  children: [
26266
26617
  " i install deps (",
@@ -26268,44 +26619,48 @@ function DetailPane({ selectedRow }) {
26268
26619
  ")"
26269
26620
  ]
26270
26621
  }),
26271
- /* @__PURE__ */ jsx2("text", {
26622
+ /* @__PURE__ */ jsx5("text", {
26272
26623
  fg: tokens.dim,
26273
26624
  children: " m rename worktree"
26274
26625
  })
26275
26626
  ]
26276
26627
  }),
26277
- /* @__PURE__ */ jsx2("text", {
26628
+ /* @__PURE__ */ jsx5("text", {
26278
26629
  fg: tokens.dim,
26279
26630
  children: " p pull branch"
26280
26631
  }),
26281
- /* @__PURE__ */ jsx2("text", {
26632
+ /* @__PURE__ */ jsx5("text", {
26282
26633
  fg: tokens.dim,
26283
26634
  children: " b rebase · s sync · o open in IDE · d remove"
26635
+ }),
26636
+ /* @__PURE__ */ jsx5("text", {
26637
+ fg: tokens.dim,
26638
+ children: " t new terminal session (max 5)"
26284
26639
  })
26285
26640
  ]
26286
26641
  })
26287
26642
  ]
26288
26643
  });
26289
26644
  }
26290
- var init_DetailPane = __esm(() => {
26645
+ var init_DetailsTab = __esm(() => {
26291
26646
  init_theme();
26292
26647
  init_use_tap();
26293
26648
  });
26294
26649
 
26295
26650
  // src/tui/components/Footer.tsx
26296
- import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
26651
+ import { jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
26297
26652
  function HintItem({ hintKey, label, isFirst, onClick }) {
26298
26653
  const tap = useTapHandler(() => onClick?.(hintKey));
26299
- return /* @__PURE__ */ jsx3("box", {
26654
+ return /* @__PURE__ */ jsx6("box", {
26300
26655
  flexDirection: "row",
26301
26656
  ...onClick ? tap : {},
26302
- children: /* @__PURE__ */ jsxs3("text", {
26657
+ children: /* @__PURE__ */ jsxs6("text", {
26303
26658
  children: [
26304
- /* @__PURE__ */ jsx3("span", {
26659
+ /* @__PURE__ */ jsx6("span", {
26305
26660
  fg: tokens.dim,
26306
26661
  children: isFirst ? "" : " · "
26307
26662
  }),
26308
- /* @__PURE__ */ jsx3("span", {
26663
+ /* @__PURE__ */ jsx6("span", {
26309
26664
  fg: tokens.dim,
26310
26665
  children: `${hintKey} ${label}`
26311
26666
  })
@@ -26315,15 +26670,15 @@ function HintItem({ hintKey, label, isFirst, onClick }) {
26315
26670
  }
26316
26671
  function ErrorBadge({ count: count2, onClick }) {
26317
26672
  const tap = useTapHandler(() => onClick?.());
26318
- return /* @__PURE__ */ jsx3("box", {
26673
+ return /* @__PURE__ */ jsx6("box", {
26319
26674
  ...onClick ? tap : {},
26320
- children: /* @__PURE__ */ jsxs3("text", {
26675
+ children: /* @__PURE__ */ jsxs6("text", {
26321
26676
  children: [
26322
- /* @__PURE__ */ jsx3("span", {
26677
+ /* @__PURE__ */ jsx6("span", {
26323
26678
  fg: tokens.error,
26324
26679
  children: `${count2} error${count2 !== 1 ? "s" : ""}`
26325
26680
  }),
26326
- /* @__PURE__ */ jsx3("span", {
26681
+ /* @__PURE__ */ jsx6("span", {
26327
26682
  fg: tokens.dim,
26328
26683
  children: " · e view"
26329
26684
  })
@@ -26333,7 +26688,7 @@ function ErrorBadge({ count: count2, onClick }) {
26333
26688
  }
26334
26689
  function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinnerFrame, filter, onHintClick, onErrorClick }) {
26335
26690
  const frame = spinnerFrame ?? "◌";
26336
- return /* @__PURE__ */ jsxs3("box", {
26691
+ return /* @__PURE__ */ jsxs6("box", {
26337
26692
  id: "footer",
26338
26693
  width: "100%",
26339
26694
  height: 3,
@@ -26344,29 +26699,29 @@ function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinner
26344
26699
  justifyContent: "space-between",
26345
26700
  alignItems: "center",
26346
26701
  children: [
26347
- busyText ? /* @__PURE__ */ jsx3("text", {
26702
+ busyText ? /* @__PURE__ */ jsx6("text", {
26348
26703
  fg: tokens.accent,
26349
26704
  children: `${frame} ${busyText}`
26350
- }) : /* @__PURE__ */ jsx3("box", {
26705
+ }) : /* @__PURE__ */ jsx6("box", {
26351
26706
  flexDirection: "row",
26352
26707
  gap: 1,
26353
- children: HINTS.map(([key, label], idx) => /* @__PURE__ */ jsx3(HintItem, {
26708
+ children: HINTS.map(([key, label], idx) => /* @__PURE__ */ jsx6(HintItem, {
26354
26709
  hintKey: key,
26355
26710
  label,
26356
26711
  isFirst: idx === 0,
26357
26712
  onClick: onHintClick
26358
26713
  }, key))
26359
26714
  }),
26360
- /* @__PURE__ */ jsxs3("box", {
26715
+ /* @__PURE__ */ jsxs6("box", {
26361
26716
  flexDirection: "row",
26362
26717
  gap: 2,
26363
26718
  alignItems: "center",
26364
26719
  children: [
26365
- message ? /* @__PURE__ */ jsx3("text", {
26720
+ message ? /* @__PURE__ */ jsx6("text", {
26366
26721
  fg: tokens.warning,
26367
26722
  children: message
26368
26723
  }) : null,
26369
- filter ? /* @__PURE__ */ jsxs3("text", {
26724
+ filter ? /* @__PURE__ */ jsxs6("text", {
26370
26725
  fg: "magenta",
26371
26726
  children: [
26372
26727
  "filter: ",
@@ -26378,17 +26733,17 @@ function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinner
26378
26733
  ")"
26379
26734
  ]
26380
26735
  }) : null,
26381
- errorCount > 0 ? /* @__PURE__ */ jsx3(ErrorBadge, {
26736
+ errorCount > 0 ? /* @__PURE__ */ jsx6(ErrorBadge, {
26382
26737
  count: errorCount,
26383
26738
  onClick: onErrorClick
26384
26739
  }) : null,
26385
- /* @__PURE__ */ jsx3(HintItem, {
26740
+ /* @__PURE__ */ jsx6(HintItem, {
26386
26741
  hintKey: "?",
26387
26742
  label: "help",
26388
26743
  isFirst: errorCount === 0,
26389
26744
  onClick: onHintClick
26390
26745
  }),
26391
- /* @__PURE__ */ jsx3("text", {
26746
+ /* @__PURE__ */ jsx6("text", {
26392
26747
  fg: tokens.dim,
26393
26748
  children: loading ? `${frame} Refreshing…` : `Updated ${lastRefreshed}`
26394
26749
  })
@@ -26408,16 +26763,16 @@ var init_Footer = __esm(() => {
26408
26763
  });
26409
26764
 
26410
26765
  // src/tui/components/Divider.tsx
26411
- import { useState as useState2, useRef as useRef4, useCallback as useCallback3 } from "react";
26766
+ import { useState as useState3, useRef as useRef5, useCallback as useCallback4 } from "react";
26412
26767
  import { useRenderer } from "@opentui/react";
26413
- import { jsx as jsx4 } from "@opentui/react/jsx-runtime";
26768
+ import { jsx as jsx7 } from "@opentui/react/jsx-runtime";
26414
26769
  function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26415
26770
  const renderer = useRenderer();
26416
- const [hovered, setHovered] = useState2(false);
26417
- const [dragging, setDragging] = useState2(false);
26418
- const startXRef = useRef4(null);
26419
- const startRatioRef = useRef4(splitRatio);
26420
- const handleMouseDown = useCallback3((e) => {
26771
+ const [hovered, setHovered] = useState3(false);
26772
+ const [dragging, setDragging] = useState3(false);
26773
+ const startXRef = useRef5(null);
26774
+ const startRatioRef = useRef5(splitRatio);
26775
+ const handleMouseDown = useCallback4((e) => {
26421
26776
  if (e.button !== 0)
26422
26777
  return;
26423
26778
  startXRef.current = e.x;
@@ -26427,7 +26782,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26427
26782
  renderer.clearSelection();
26428
26783
  e.stopPropagation();
26429
26784
  }, [splitRatio, onDraggingChange, renderer]);
26430
- const handleMouseUp = useCallback3((e) => {
26785
+ const handleMouseUp = useCallback4((e) => {
26431
26786
  if (e.button !== 0)
26432
26787
  return;
26433
26788
  startXRef.current = null;
@@ -26435,7 +26790,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26435
26790
  onDraggingChange?.(false);
26436
26791
  e.stopPropagation();
26437
26792
  }, [onDraggingChange]);
26438
- const handleMouseDrag = useCallback3((e) => {
26793
+ const handleMouseDrag = useCallback4((e) => {
26439
26794
  if (startXRef.current === null)
26440
26795
  return;
26441
26796
  renderer.clearSelection();
@@ -26450,7 +26805,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26450
26805
  onMouseDrag: handleMouseDrag,
26451
26806
  onMouseMove: handleMouseDrag
26452
26807
  };
26453
- return /* @__PURE__ */ jsx4("box", {
26808
+ return /* @__PURE__ */ jsx7("box", {
26454
26809
  width: 3,
26455
26810
  height: "100%",
26456
26811
  backgroundColor: dragging ? tokens.selectionBg : hovered ? tokens.panelBg : undefined,
@@ -26459,7 +26814,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26459
26814
  onMouseOut: () => setHovered(false),
26460
26815
  alignItems: "center",
26461
26816
  justifyContent: "center",
26462
- children: /* @__PURE__ */ jsx4("text", {
26817
+ children: /* @__PURE__ */ jsx7("text", {
26463
26818
  fg: dragging ? tokens.accent : hovered ? tokens.borderActive : tokens.border,
26464
26819
  ...dragHandlers,
26465
26820
  onMouseOver: () => setHovered(true),
@@ -26474,9 +26829,9 @@ var init_Divider = __esm(() => {
26474
26829
  });
26475
26830
 
26476
26831
  // src/tui/components/Overlay.tsx
26477
- import { jsx as jsx5 } from "@opentui/react/jsx-runtime";
26832
+ import { jsx as jsx8 } from "@opentui/react/jsx-runtime";
26478
26833
  function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
26479
- return /* @__PURE__ */ jsx5("box", {
26834
+ return /* @__PURE__ */ jsx8("box", {
26480
26835
  id: "overlay-scrim",
26481
26836
  position: "absolute",
26482
26837
  width: "100%",
@@ -26488,7 +26843,7 @@ function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
26488
26843
  flexDirection: "row",
26489
26844
  justifyContent: "center",
26490
26845
  alignItems: "center",
26491
- children: /* @__PURE__ */ jsx5("box", {
26846
+ children: /* @__PURE__ */ jsx8("box", {
26492
26847
  id: "overlay-panel",
26493
26848
  width,
26494
26849
  border: true,
@@ -26508,37 +26863,38 @@ var init_Overlay = __esm(() => {
26508
26863
  });
26509
26864
 
26510
26865
  // src/tui/components/HelpOverlay.tsx
26511
- import { jsx as jsx6, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
26866
+ import { jsx as jsx9, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
26512
26867
  function HelpOverlay() {
26513
- return /* @__PURE__ */ jsxs4(Overlay, {
26868
+ return /* @__PURE__ */ jsxs7(Overlay, {
26514
26869
  title: "Help",
26515
26870
  borderColor: tokens.border,
26516
26871
  width: 84,
26517
26872
  children: [
26518
- /* @__PURE__ */ jsx6("box", {
26873
+ /* @__PURE__ */ jsx9("box", {
26519
26874
  flexDirection: "column",
26520
26875
  style: { maxHeight: 36 },
26521
- children: /* @__PURE__ */ jsx6("scrollbox", {
26876
+ children: /* @__PURE__ */ jsx9("scrollbox", {
26522
26877
  flexGrow: 1,
26523
- children: HELP_ENTRIES.map(([key, desc]) => /* @__PURE__ */ jsxs4("box", {
26878
+ children: HELP_ENTRIES.map(([key, desc]) => /* @__PURE__ */ jsxs7("box", {
26524
26879
  flexDirection: "row",
26525
26880
  gap: 1,
26526
26881
  children: [
26527
- /* @__PURE__ */ jsx6("box", {
26882
+ /* @__PURE__ */ jsx9("box", {
26528
26883
  width: 18,
26529
26884
  flexShrink: 0,
26530
- children: /* @__PURE__ */ jsx6("text", {
26885
+ justifyContent: "flex-end",
26886
+ children: /* @__PURE__ */ jsx9("text", {
26531
26887
  fg: tokens.accent,
26532
26888
  children: key
26533
26889
  })
26534
26890
  }),
26535
- /* @__PURE__ */ jsx6("text", {
26891
+ /* @__PURE__ */ jsx9("text", {
26536
26892
  fg: tokens.dim,
26537
26893
  children: "-"
26538
26894
  }),
26539
- /* @__PURE__ */ jsx6("box", {
26895
+ /* @__PURE__ */ jsx9("box", {
26540
26896
  flexGrow: 1,
26541
- children: /* @__PURE__ */ jsx6("text", {
26897
+ children: /* @__PURE__ */ jsx9("text", {
26542
26898
  fg: tokens.fg,
26543
26899
  children: desc
26544
26900
  })
@@ -26547,7 +26903,7 @@ function HelpOverlay() {
26547
26903
  }, key))
26548
26904
  })
26549
26905
  }),
26550
- /* @__PURE__ */ jsx6("text", {
26906
+ /* @__PURE__ */ jsx9("text", {
26551
26907
  style: { marginTop: 1, fg: tokens.dim },
26552
26908
  children: "Press any key to close"
26553
26909
  })
@@ -26572,7 +26928,9 @@ var init_HelpOverlay = __esm(() => {
26572
26928
  ["i", "Install dependencies in selection (worktrees and main)"],
26573
26929
  ["m", "Rename selected worktree (branch + directory)"],
26574
26930
  ["o", "Open selected in IDE"],
26575
- ["a", "Spawn agent in selected"],
26931
+ ["t", "New terminal session (max 5 per worktree, Details ↔ Session tabs)"],
26932
+ ["click tab", "Switch Details ↔ Session (professional tabs with ▎ active)"],
26933
+ ["Ctrl+G / click table", "Leave terminal focus — focused terminal gets all keys"],
26576
26934
  ["d", "Remove selected worktree(s)"],
26577
26935
  ["e", "View data warnings (when count > 0)"],
26578
26936
  ["r", "Refresh data"],
@@ -26586,50 +26944,50 @@ var init_HelpOverlay = __esm(() => {
26586
26944
  });
26587
26945
 
26588
26946
  // src/tui/components/ConfirmModal.tsx
26589
- import { jsx as jsx7, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
26947
+ import { jsx as jsx10, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
26590
26948
  function ConfirmModal({ title, message, onConfirm, onCancel }) {
26591
26949
  const yesTap = useTapHandler(() => onConfirm?.());
26592
26950
  const noTap = useTapHandler(() => onCancel?.());
26593
- return /* @__PURE__ */ jsx7(Overlay, {
26951
+ return /* @__PURE__ */ jsx10(Overlay, {
26594
26952
  title,
26595
26953
  borderColor: tokens.warning,
26596
- children: /* @__PURE__ */ jsxs5("box", {
26954
+ children: /* @__PURE__ */ jsxs8("box", {
26597
26955
  flexDirection: "column",
26598
26956
  justifyContent: "center",
26599
26957
  alignItems: "center",
26600
26958
  children: [
26601
- /* @__PURE__ */ jsx7("text", {
26959
+ /* @__PURE__ */ jsx10("text", {
26602
26960
  children: message
26603
26961
  }),
26604
- /* @__PURE__ */ jsxs5("box", {
26962
+ /* @__PURE__ */ jsxs8("box", {
26605
26963
  flexDirection: "row",
26606
26964
  gap: 2,
26607
26965
  style: { marginTop: 2 },
26608
26966
  children: [
26609
- /* @__PURE__ */ jsx7("box", {
26967
+ /* @__PURE__ */ jsx10("box", {
26610
26968
  ...onConfirm ? yesTap : {},
26611
- children: /* @__PURE__ */ jsxs5("text", {
26969
+ children: /* @__PURE__ */ jsxs8("text", {
26612
26970
  children: [
26613
- /* @__PURE__ */ jsx7("span", {
26971
+ /* @__PURE__ */ jsx10("span", {
26614
26972
  fg: tokens.dim,
26615
26973
  children: "[y] "
26616
26974
  }),
26617
- /* @__PURE__ */ jsx7("span", {
26975
+ /* @__PURE__ */ jsx10("span", {
26618
26976
  fg: tokens.success,
26619
26977
  children: "Yes"
26620
26978
  })
26621
26979
  ]
26622
26980
  })
26623
26981
  }),
26624
- /* @__PURE__ */ jsx7("box", {
26982
+ /* @__PURE__ */ jsx10("box", {
26625
26983
  ...onCancel ? noTap : {},
26626
- children: /* @__PURE__ */ jsxs5("text", {
26984
+ children: /* @__PURE__ */ jsxs8("text", {
26627
26985
  children: [
26628
- /* @__PURE__ */ jsx7("span", {
26986
+ /* @__PURE__ */ jsx10("span", {
26629
26987
  fg: tokens.dim,
26630
26988
  children: "[n/esc] "
26631
26989
  }),
26632
- /* @__PURE__ */ jsx7("span", {
26990
+ /* @__PURE__ */ jsx10("span", {
26633
26991
  children: "Cancel"
26634
26992
  })
26635
26993
  ]
@@ -26705,52 +27063,52 @@ async function runWtxAction(args, onLine) {
26705
27063
  }
26706
27064
 
26707
27065
  // src/tui/components/ActionLogModal.tsx
26708
- import { jsx as jsx8, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
27066
+ import { jsx as jsx11, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
26709
27067
  function ActionLogModal({ title, lines, done, exitCode, remaining }) {
26710
- return /* @__PURE__ */ jsxs6(Overlay, {
27068
+ return /* @__PURE__ */ jsxs9(Overlay, {
26711
27069
  title,
26712
27070
  borderColor: tokens.border,
26713
27071
  children: [
26714
- /* @__PURE__ */ jsx8("box", {
27072
+ /* @__PURE__ */ jsx11("box", {
26715
27073
  flexGrow: 1,
26716
27074
  flexDirection: "column",
26717
27075
  style: { minHeight: 10, maxHeight: 20 },
26718
- children: /* @__PURE__ */ jsx8("scrollbox", {
27076
+ children: /* @__PURE__ */ jsx11("scrollbox", {
26719
27077
  flexGrow: 1,
26720
27078
  stickyScroll: true,
26721
- children: lines.map((line, i2) => /* @__PURE__ */ jsx8("text", {
27079
+ children: lines.map((line, i2) => /* @__PURE__ */ jsx11("text", {
26722
27080
  fg: line.type === "err" ? tokens.error : tokens.fg,
26723
27081
  children: line.text
26724
27082
  }, i2))
26725
27083
  })
26726
27084
  }),
26727
- /* @__PURE__ */ jsxs6("box", {
27085
+ /* @__PURE__ */ jsxs9("box", {
26728
27086
  marginTop: 1,
26729
27087
  flexDirection: "row",
26730
27088
  justifyContent: "center",
26731
27089
  children: [
26732
- !done && /* @__PURE__ */ jsx8("text", {
27090
+ !done && /* @__PURE__ */ jsx11("text", {
26733
27091
  fg: tokens.warning,
26734
27092
  children: "Running..."
26735
27093
  }),
26736
- done && exitCode === 0 && /* @__PURE__ */ jsx8("text", {
27094
+ done && exitCode === 0 && /* @__PURE__ */ jsx11("text", {
26737
27095
  fg: tokens.success,
26738
27096
  children: "✓ Done (exit 0)"
26739
27097
  }),
26740
- done && exitCode !== 0 && /* @__PURE__ */ jsxs6("text", {
27098
+ done && exitCode !== 0 && /* @__PURE__ */ jsxs9("text", {
26741
27099
  children: [
26742
- /* @__PURE__ */ jsxs6("span", {
27100
+ /* @__PURE__ */ jsxs9("span", {
26743
27101
  fg: tokens.error,
26744
27102
  children: [
26745
27103
  "✗ Exit ",
26746
27104
  exitCode
26747
27105
  ]
26748
27106
  }),
26749
- remaining !== undefined && remaining > 0 && /* @__PURE__ */ jsx8("span", {
27107
+ remaining !== undefined && remaining > 0 && /* @__PURE__ */ jsx11("span", {
26750
27108
  fg: tokens.warning,
26751
27109
  children: ` · ${remaining} more failure${remaining > 1 ? "s" : ""}`
26752
27110
  }),
26753
- /* @__PURE__ */ jsx8("span", {
27111
+ /* @__PURE__ */ jsx11("span", {
26754
27112
  fg: tokens.dim,
26755
27113
  children: " - press any key to close"
26756
27114
  })
@@ -26837,8 +27195,8 @@ var init_history = __esm(() => {
26837
27195
  });
26838
27196
 
26839
27197
  // src/tui/components/HistoryOverlay.tsx
26840
- import { useEffect as useEffect3, useState as useState3 } from "react";
26841
- import { jsx as jsx9, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
27198
+ import { useEffect as useEffect4, useState as useState4 } from "react";
27199
+ import { jsx as jsx12, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
26842
27200
  function formatShortTime(iso) {
26843
27201
  const d = new Date(iso);
26844
27202
  if (Number.isNaN(d.getTime()))
@@ -26851,16 +27209,16 @@ function HistoryEntryLine({ entry }) {
26851
27209
  const duration3 = entry.durationMs !== null && entry.durationMs !== undefined ? ` ${entry.durationMs}ms` : "";
26852
27210
  const prefix = `${formatShortTime(entry.ts)} ${entry.source.padEnd(8)} ${args}`;
26853
27211
  if (entry.exit === null || entry.exit === undefined) {
26854
- return /* @__PURE__ */ jsxs7("text", {
27212
+ return /* @__PURE__ */ jsxs10("text", {
26855
27213
  children: [
26856
- /* @__PURE__ */ jsxs7("span", {
27214
+ /* @__PURE__ */ jsxs10("span", {
26857
27215
  fg: tokens.dim,
26858
27216
  children: [
26859
27217
  "◌ ",
26860
27218
  prefix
26861
27219
  ]
26862
27220
  }),
26863
- duration3 && /* @__PURE__ */ jsx9("span", {
27221
+ duration3 && /* @__PURE__ */ jsx12("span", {
26864
27222
  fg: tokens.dim,
26865
27223
  children: duration3
26866
27224
  })
@@ -26868,32 +27226,32 @@ function HistoryEntryLine({ entry }) {
26868
27226
  });
26869
27227
  }
26870
27228
  if (entry.exit === 0) {
26871
- return /* @__PURE__ */ jsxs7("text", {
27229
+ return /* @__PURE__ */ jsxs10("text", {
26872
27230
  children: [
26873
- /* @__PURE__ */ jsxs7("span", {
27231
+ /* @__PURE__ */ jsxs10("span", {
26874
27232
  fg: tokens.success,
26875
27233
  children: [
26876
27234
  "✓ ",
26877
27235
  prefix
26878
27236
  ]
26879
27237
  }),
26880
- duration3 && /* @__PURE__ */ jsx9("span", {
27238
+ duration3 && /* @__PURE__ */ jsx12("span", {
26881
27239
  fg: tokens.dim,
26882
27240
  children: duration3
26883
27241
  })
26884
27242
  ]
26885
27243
  });
26886
27244
  }
26887
- return /* @__PURE__ */ jsxs7("text", {
27245
+ return /* @__PURE__ */ jsxs10("text", {
26888
27246
  children: [
26889
- /* @__PURE__ */ jsx9("span", {
27247
+ /* @__PURE__ */ jsx12("span", {
26890
27248
  fg: tokens.error,
26891
27249
  children: "✗ "
26892
27250
  }),
26893
- /* @__PURE__ */ jsx9("span", {
27251
+ /* @__PURE__ */ jsx12("span", {
26894
27252
  children: prefix
26895
27253
  }),
26896
- /* @__PURE__ */ jsxs7("span", {
27254
+ /* @__PURE__ */ jsxs10("span", {
26897
27255
  fg: tokens.dim,
26898
27256
  children: [
26899
27257
  " (exit ",
@@ -26906,32 +27264,32 @@ function HistoryEntryLine({ entry }) {
26906
27264
  });
26907
27265
  }
26908
27266
  function HistoryOverlay() {
26909
- const [entries, setEntries] = useState3([]);
26910
- useEffect3(() => {
27267
+ const [entries, setEntries] = useState4([]);
27268
+ useEffect4(() => {
26911
27269
  setEntries(readRecentHistory(500));
26912
27270
  }, []);
26913
- return /* @__PURE__ */ jsxs7(Overlay, {
27271
+ return /* @__PURE__ */ jsxs10(Overlay, {
26914
27272
  title: "Action History",
26915
27273
  borderColor: tokens.border,
26916
27274
  width: 110,
26917
27275
  children: [
26918
- /* @__PURE__ */ jsx9("box", {
27276
+ /* @__PURE__ */ jsx12("box", {
26919
27277
  flexGrow: 1,
26920
27278
  flexDirection: "column",
26921
27279
  style: { minHeight: 30, maxHeight: 46 },
26922
- children: entries.length === 0 ? /* @__PURE__ */ jsx9("text", {
27280
+ children: entries.length === 0 ? /* @__PURE__ */ jsx12("text", {
26923
27281
  fg: tokens.dim,
26924
27282
  children: "No actions recorded yet."
26925
- }) : /* @__PURE__ */ jsx9("scrollbox", {
27283
+ }) : /* @__PURE__ */ jsx12("scrollbox", {
26926
27284
  flexGrow: 1,
26927
- children: entries.map((entry, i2) => /* @__PURE__ */ jsx9(HistoryEntryLine, {
27285
+ children: entries.map((entry, i2) => /* @__PURE__ */ jsx12(HistoryEntryLine, {
26928
27286
  entry
26929
27287
  }, i2))
26930
27288
  })
26931
27289
  }),
26932
- /* @__PURE__ */ jsx9("box", {
27290
+ /* @__PURE__ */ jsx12("box", {
26933
27291
  marginTop: 1,
26934
- children: /* @__PURE__ */ jsx9("text", {
27292
+ children: /* @__PURE__ */ jsx12("text", {
26935
27293
  fg: tokens.dim,
26936
27294
  children: "Press any key to close · newest first"
26937
27295
  })
@@ -26946,28 +27304,28 @@ var init_HistoryOverlay = __esm(() => {
26946
27304
  });
26947
27305
 
26948
27306
  // src/tui/components/InputModal.tsx
26949
- import { useState as useState4 } from "react";
26950
- import { jsx as jsx10, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
27307
+ import { useState as useState5 } from "react";
27308
+ import { jsx as jsx13, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
26951
27309
  function InputModal({ title, placeholder, initialValue, errorMessage: errorMessage2, onSubmit }) {
26952
- const [value, setValue] = useState4(initialValue ?? "");
26953
- return /* @__PURE__ */ jsx10(Overlay, {
27310
+ const [value, setValue] = useState5(initialValue ?? "");
27311
+ return /* @__PURE__ */ jsx13(Overlay, {
26954
27312
  title,
26955
27313
  borderColor: tokens.accent,
26956
- children: /* @__PURE__ */ jsxs8("box", {
27314
+ children: /* @__PURE__ */ jsxs11("box", {
26957
27315
  flexDirection: "column",
26958
27316
  children: [
26959
- /* @__PURE__ */ jsx10("input", {
27317
+ /* @__PURE__ */ jsx13("input", {
26960
27318
  focused: true,
26961
27319
  value: initialValue ?? "",
26962
27320
  placeholder,
26963
27321
  onInput: (val) => setValue(val),
26964
27322
  onSubmit: () => onSubmit(value)
26965
27323
  }),
26966
- errorMessage2 ? /* @__PURE__ */ jsx10("text", {
27324
+ errorMessage2 ? /* @__PURE__ */ jsx13("text", {
26967
27325
  fg: tokens.error,
26968
27326
  style: { marginTop: 1 },
26969
27327
  children: errorMessage2
26970
- }) : /* @__PURE__ */ jsx10("text", {
27328
+ }) : /* @__PURE__ */ jsx13("text", {
26971
27329
  fg: tokens.dim,
26972
27330
  style: { marginTop: 1 },
26973
27331
  children: "Press Enter to submit, empty to cancel"
@@ -26982,23 +27340,23 @@ var init_InputModal = __esm(() => {
26982
27340
  });
26983
27341
 
26984
27342
  // src/tui/components/ChoiceModal.tsx
26985
- import { useState as useState5 } from "react";
27343
+ import { useState as useState6 } from "react";
26986
27344
  import { useKeyboard } from "@opentui/react";
26987
- import { jsx as jsx11, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
27345
+ import { jsx as jsx14, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
26988
27346
  function ChoiceOptionRow({ option, index, isSelected, onSelect }) {
26989
27347
  const tap = useTapHandler(() => onSelect(option.value));
26990
- return /* @__PURE__ */ jsxs9("box", {
27348
+ return /* @__PURE__ */ jsxs12("box", {
26991
27349
  flexDirection: "column",
26992
27350
  backgroundColor: isSelected ? tokens.selectionBg : undefined,
26993
27351
  ...tap,
26994
27352
  children: [
26995
- /* @__PURE__ */ jsx11("text", {
26996
- children: /* @__PURE__ */ jsx11("span", {
27353
+ /* @__PURE__ */ jsx14("text", {
27354
+ children: /* @__PURE__ */ jsx14("span", {
26997
27355
  fg: isSelected ? tokens.bright : tokens.fg,
26998
27356
  children: `${index + 1}. ${option.label}`
26999
27357
  })
27000
27358
  }),
27001
- option.desc && /* @__PURE__ */ jsx11("text", {
27359
+ option.desc && /* @__PURE__ */ jsx14("text", {
27002
27360
  fg: tokens.dim,
27003
27361
  children: ` ${option.desc}`
27004
27362
  })
@@ -27006,7 +27364,7 @@ function ChoiceOptionRow({ option, index, isSelected, onSelect }) {
27006
27364
  });
27007
27365
  }
27008
27366
  function ChoiceModal({ title, options, initialIndex = 0, onSubmit, onCancel }) {
27009
- const [index, setIndex] = useState5(Math.min(Math.max(initialIndex, 0), options.length - 1));
27367
+ const [index, setIndex] = useState6(Math.min(Math.max(initialIndex, 0), options.length - 1));
27010
27368
  useKeyboard((key) => {
27011
27369
  if (key.name === "escape") {
27012
27370
  onCancel();
@@ -27033,19 +27391,19 @@ function ChoiceModal({ title, options, initialIndex = 0, onSubmit, onCancel }) {
27033
27391
  onSubmit(option.value);
27034
27392
  }
27035
27393
  });
27036
- return /* @__PURE__ */ jsx11(Overlay, {
27394
+ return /* @__PURE__ */ jsx14(Overlay, {
27037
27395
  title,
27038
27396
  borderColor: tokens.accent,
27039
- children: /* @__PURE__ */ jsxs9("box", {
27397
+ children: /* @__PURE__ */ jsxs12("box", {
27040
27398
  flexDirection: "column",
27041
27399
  children: [
27042
- options.map((option, i2) => /* @__PURE__ */ jsx11(ChoiceOptionRow, {
27400
+ options.map((option, i2) => /* @__PURE__ */ jsx14(ChoiceOptionRow, {
27043
27401
  option,
27044
27402
  index: i2,
27045
27403
  isSelected: i2 === index,
27046
27404
  onSelect: onSubmit
27047
27405
  }, option.value)),
27048
- /* @__PURE__ */ jsxs9("text", {
27406
+ /* @__PURE__ */ jsxs12("text", {
27049
27407
  fg: tokens.dim,
27050
27408
  style: { marginTop: 1 },
27051
27409
  children: [
@@ -27065,16 +27423,16 @@ var init_ChoiceModal = __esm(() => {
27065
27423
  });
27066
27424
 
27067
27425
  // src/tui/components/ConfigOverlay.tsx
27068
- import { useState as useState6, useEffect as useEffect4 } from "react";
27426
+ import { useState as useState7, useEffect as useEffect5 } from "react";
27069
27427
  import { useKeyboard as useKeyboard2 } from "@opentui/react";
27070
- import { jsx as jsx12, jsxs as jsxs10, Fragment as Fragment2 } from "@opentui/react/jsx-runtime";
27428
+ import { jsx as jsx15, jsxs as jsxs13, Fragment as Fragment2 } from "@opentui/react/jsx-runtime";
27071
27429
  function ConfigOverlay({ onClose, onSaved, onError }) {
27072
- const [config2, setConfig] = useState6(null);
27073
- const [viewState, setViewState] = useState6({ type: "main" });
27074
- const [selectedIndex, setSelectedIndex] = useState6(1);
27075
- const [windowStart, setWindowStart] = useState6(0);
27076
- const [editState, setEditState] = useState6({ type: "none" });
27077
- useEffect4(() => {
27430
+ const [config2, setConfig] = useState7(null);
27431
+ const [viewState, setViewState] = useState7({ type: "main" });
27432
+ const [selectedIndex, setSelectedIndex] = useState7(1);
27433
+ const [windowStart, setWindowStart] = useState7(0);
27434
+ const [editState, setEditState] = useState7({ type: "none" });
27435
+ useEffect5(() => {
27078
27436
  try {
27079
27437
  setConfig(loadConfig());
27080
27438
  } catch (err) {
@@ -27110,7 +27468,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27110
27468
  detailRows.push({ type: "remove_repo" });
27111
27469
  }
27112
27470
  const activeRows = viewState.type === "main" ? mainRows : detailRows;
27113
- useEffect4(() => {
27471
+ useEffect5(() => {
27114
27472
  if (!activeRows || selectedIndex >= activeRows.length) {
27115
27473
  let newIdx = activeRows.length - 1;
27116
27474
  while (newIdx > 0 && activeRows[newIdx]?.type === "header") {
@@ -27330,17 +27688,17 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27330
27688
  hint = "Remove this repository from config";
27331
27689
  }
27332
27690
  }
27333
- return /* @__PURE__ */ jsxs10(Fragment2, {
27691
+ return /* @__PURE__ */ jsxs13(Fragment2, {
27334
27692
  children: [
27335
- /* @__PURE__ */ jsx12(Overlay, {
27693
+ /* @__PURE__ */ jsx15(Overlay, {
27336
27694
  title: "Configuration",
27337
27695
  borderColor: tokens.border,
27338
- children: /* @__PURE__ */ jsxs10("box", {
27696
+ children: /* @__PURE__ */ jsxs13("box", {
27339
27697
  flexDirection: "column",
27340
27698
  width: "100%",
27341
27699
  height: 24,
27342
27700
  children: [
27343
- /* @__PURE__ */ jsx12("box", {
27701
+ /* @__PURE__ */ jsx15("box", {
27344
27702
  flexDirection: "column",
27345
27703
  flexGrow: 1,
27346
27704
  overflow: "hidden",
@@ -27348,7 +27706,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27348
27706
  const i2 = windowStart + idx;
27349
27707
  const isSelected = i2 === selectedIndex;
27350
27708
  if (row.type === "header") {
27351
- return /* @__PURE__ */ jsx12("text", {
27709
+ return /* @__PURE__ */ jsx15("text", {
27352
27710
  fg: tokens.dim,
27353
27711
  style: { marginTop: i2 === 0 ? 0 : 1 },
27354
27712
  children: row.label
@@ -27377,11 +27735,11 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27377
27735
  labelText = "Remove this repo";
27378
27736
  valText = "";
27379
27737
  }
27380
- return /* @__PURE__ */ jsxs10("box", {
27738
+ return /* @__PURE__ */ jsxs13("box", {
27381
27739
  flexDirection: "row",
27382
27740
  width: "100%",
27383
27741
  children: [
27384
- /* @__PURE__ */ jsxs10("text", {
27742
+ /* @__PURE__ */ jsxs13("text", {
27385
27743
  fg: isSelected ? tokens.bright : tokens.fg,
27386
27744
  bg: isSelected ? tokens.selectionBg : undefined,
27387
27745
  width: 24,
@@ -27390,7 +27748,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27390
27748
  labelText
27391
27749
  ]
27392
27750
  }),
27393
- valText && /* @__PURE__ */ jsx12("text", {
27751
+ valText && /* @__PURE__ */ jsx15("text", {
27394
27752
  fg: isSelected ? tokens.accent : tokens.dim,
27395
27753
  bg: isSelected ? tokens.selectionBg : undefined,
27396
27754
  flexShrink: 1,
@@ -27400,14 +27758,14 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27400
27758
  }, i2);
27401
27759
  })
27402
27760
  }),
27403
- /* @__PURE__ */ jsx12("box", {
27761
+ /* @__PURE__ */ jsx15("box", {
27404
27762
  flexDirection: "row",
27405
27763
  border: true,
27406
27764
  borderColor: tokens.border,
27407
27765
  paddingTop: 1,
27408
27766
  marginTop: 1,
27409
27767
  minHeight: 3,
27410
- children: /* @__PURE__ */ jsx12("text", {
27768
+ children: /* @__PURE__ */ jsx15("text", {
27411
27769
  fg: tokens.dim,
27412
27770
  children: hint
27413
27771
  })
@@ -27415,14 +27773,14 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27415
27773
  ]
27416
27774
  })
27417
27775
  }),
27418
- editState.type === "input" && /* @__PURE__ */ jsx12(InputModal, {
27776
+ editState.type === "input" && /* @__PURE__ */ jsx15(InputModal, {
27419
27777
  title: editState.title,
27420
27778
  initialValue: editState.currentValue,
27421
27779
  placeholder: "Enter value...",
27422
27780
  errorMessage: editState.error,
27423
27781
  onSubmit: handleInputSubmit
27424
27782
  }),
27425
- editState.type === "confirm_remove" && /* @__PURE__ */ jsx12(ConfirmModal, {
27783
+ editState.type === "confirm_remove" && /* @__PURE__ */ jsx15(ConfirmModal, {
27426
27784
  title: "Remove Repo",
27427
27785
  message: `Remove repo ${editState.repo} from config?`
27428
27786
  })
@@ -27439,29 +27797,29 @@ var init_ConfigOverlay = __esm(() => {
27439
27797
  });
27440
27798
 
27441
27799
  // src/tui/components/WarningsOverlay.tsx
27442
- import { jsx as jsx13, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
27800
+ import { jsx as jsx16, jsxs as jsxs14 } from "@opentui/react/jsx-runtime";
27443
27801
  function WarningsOverlay({ warnings, onAcknowledge, onClose }) {
27444
27802
  const ackTap = useTapHandler(() => onAcknowledge?.());
27445
27803
  const closeTap = useTapHandler(() => onClose?.());
27446
- return /* @__PURE__ */ jsxs11(Overlay, {
27804
+ return /* @__PURE__ */ jsxs14(Overlay, {
27447
27805
  title: `Warnings (${warnings.length})`,
27448
27806
  borderColor: tokens.warning,
27449
27807
  width: 80,
27450
27808
  children: [
27451
- /* @__PURE__ */ jsx13("box", {
27809
+ /* @__PURE__ */ jsx16("box", {
27452
27810
  flexDirection: "column",
27453
27811
  style: { maxHeight: 28 },
27454
- children: /* @__PURE__ */ jsx13("scrollbox", {
27812
+ children: /* @__PURE__ */ jsx16("scrollbox", {
27455
27813
  flexGrow: 1,
27456
- children: warnings.map((warning, i2) => /* @__PURE__ */ jsxs11("box", {
27814
+ children: warnings.map((warning, i2) => /* @__PURE__ */ jsxs14("box", {
27457
27815
  flexDirection: "column",
27458
27816
  style: { marginBottom: 1 },
27459
27817
  children: [
27460
- /* @__PURE__ */ jsx13("text", {
27818
+ /* @__PURE__ */ jsx16("text", {
27461
27819
  fg: tokens.warning,
27462
27820
  children: `⚠ ${warning.repoName}`
27463
27821
  }),
27464
- wrapText(warning.message, 72).map((line, j) => /* @__PURE__ */ jsx13("text", {
27822
+ wrapText(warning.message, 72).map((line, j) => /* @__PURE__ */ jsx16("text", {
27465
27823
  fg: tokens.fg,
27466
27824
  children: ` ${line}`
27467
27825
  }, j))
@@ -27469,36 +27827,36 @@ function WarningsOverlay({ warnings, onAcknowledge, onClose }) {
27469
27827
  }, i2))
27470
27828
  })
27471
27829
  }),
27472
- /* @__PURE__ */ jsxs11("box", {
27830
+ /* @__PURE__ */ jsxs14("box", {
27473
27831
  flexDirection: "row",
27474
27832
  gap: 3,
27475
27833
  style: { marginTop: 2 },
27476
27834
  justifyContent: "center",
27477
27835
  children: [
27478
- /* @__PURE__ */ jsx13("box", {
27836
+ /* @__PURE__ */ jsx16("box", {
27479
27837
  ...ackTap,
27480
- children: /* @__PURE__ */ jsxs11("text", {
27838
+ children: /* @__PURE__ */ jsxs14("text", {
27481
27839
  children: [
27482
- /* @__PURE__ */ jsx13("span", {
27840
+ /* @__PURE__ */ jsx16("span", {
27483
27841
  fg: tokens.dim,
27484
27842
  children: "[a] "
27485
27843
  }),
27486
- /* @__PURE__ */ jsx13("span", {
27844
+ /* @__PURE__ */ jsx16("span", {
27487
27845
  fg: tokens.success,
27488
27846
  children: "Acknowledge"
27489
27847
  })
27490
27848
  ]
27491
27849
  })
27492
27850
  }),
27493
- /* @__PURE__ */ jsx13("box", {
27851
+ /* @__PURE__ */ jsx16("box", {
27494
27852
  ...closeTap,
27495
- children: /* @__PURE__ */ jsxs11("text", {
27853
+ children: /* @__PURE__ */ jsxs14("text", {
27496
27854
  children: [
27497
- /* @__PURE__ */ jsx13("span", {
27855
+ /* @__PURE__ */ jsx16("span", {
27498
27856
  fg: tokens.dim,
27499
27857
  children: "[esc] "
27500
27858
  }),
27501
- /* @__PURE__ */ jsx13("span", {
27859
+ /* @__PURE__ */ jsx16("span", {
27502
27860
  children: "Close"
27503
27861
  })
27504
27862
  ]
@@ -27506,7 +27864,7 @@ function WarningsOverlay({ warnings, onAcknowledge, onClose }) {
27506
27864
  })
27507
27865
  ]
27508
27866
  }),
27509
- /* @__PURE__ */ jsx13("text", {
27867
+ /* @__PURE__ */ jsx16("text", {
27510
27868
  style: { marginTop: 1, fg: tokens.dim },
27511
27869
  children: "Press a to acknowledge and clear errors"
27512
27870
  })
@@ -27521,10 +27879,10 @@ var init_WarningsOverlay = __esm(() => {
27521
27879
  });
27522
27880
 
27523
27881
  // src/tui/hooks/useSpinnerFrame.ts
27524
- import { useEffect as useEffect5, useState as useState7 } from "react";
27882
+ import { useEffect as useEffect6, useState as useState8 } from "react";
27525
27883
  function useSpinnerFrame(active) {
27526
- const [index, setIndex] = useState7(0);
27527
- useEffect5(() => {
27884
+ const [index, setIndex] = useState8(0);
27885
+ useEffect6(() => {
27528
27886
  if (!active)
27529
27887
  return;
27530
27888
  const id = setInterval(() => setIndex((i2) => (i2 + 1) % FRAMES.length), INTERVAL_MS);
@@ -27537,11 +27895,408 @@ var init_useSpinnerFrame = __esm(() => {
27537
27895
  FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
27538
27896
  });
27539
27897
 
27898
+ // src/tui/hooks/useTerminalSessions.ts
27899
+ import { useState as useState9, useCallback as useCallback5, useRef as useRef6, useEffect as useEffect7 } from "react";
27900
+ function worktreeKeyFor(repoName, branch, path37) {
27901
+ return path37 || `${repoName}:${branch}`;
27902
+ }
27903
+ function nextTerminalSessionLabel(existingCount) {
27904
+ return `Session ${existingCount + 1}`;
27905
+ }
27906
+ function isSameSize(session, cols, rows) {
27907
+ return session.cols === cols && session.rows === rows;
27908
+ }
27909
+ function updateSessionSize(session, cols, rows) {
27910
+ return isSameSize(session, cols, rows) ? session : { ...session, cols, rows };
27911
+ }
27912
+ function relabelTerminalSessions(sessions) {
27913
+ return sessions.map((session, index) => ({ ...session, label: nextTerminalSessionLabel(index) }));
27914
+ }
27915
+ function useTerminalSessions() {
27916
+ const [sessionsByKey, setSessionsByKey] = useState9(() => new Map);
27917
+ const procsRef = useRef6(new Map);
27918
+ const listenersRef = useRef6(new Map);
27919
+ const rawBuffersRef = useRef6(new Map);
27920
+ const getSessions = useCallback5((repoName, branch, path37) => {
27921
+ const key = worktreeKeyFor(repoName, branch, path37);
27922
+ return sessionsByKey.get(key) ?? [];
27923
+ }, [sessionsByKey]);
27924
+ const getKey = useCallback5((repoName, branch, path37) => worktreeKeyFor(repoName, branch, path37), []);
27925
+ const createSession = useCallback5((repoName, branch, path37, opts) => {
27926
+ const key = worktreeKeyFor(repoName, branch, path37);
27927
+ const existing = sessionsByKey.get(key) ?? [];
27928
+ if (existing.length >= MAX_TERMINAL_SESSIONS) {
27929
+ return { session: null, error: `Max ${MAX_TERMINAL_SESSIONS} sessions per worktree` };
27930
+ }
27931
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
27932
+ const label = nextTerminalSessionLabel(existing.length);
27933
+ const cols = opts?.cols ?? 80;
27934
+ const rows = opts?.rows ?? 24;
27935
+ const session = {
27936
+ id,
27937
+ label,
27938
+ worktreeKey: key,
27939
+ worktreePath: path37,
27940
+ repoName,
27941
+ branch,
27942
+ lines: [],
27943
+ inputBuffer: "",
27944
+ exited: null,
27945
+ proc: null,
27946
+ terminal: null,
27947
+ cols,
27948
+ rows,
27949
+ usePty: false
27950
+ };
27951
+ const shell = process.env.SHELL || "/bin/bash";
27952
+ const appendPipe = (data) => {
27953
+ const text = new TextDecoder().decode(data);
27954
+ const parts = text.split(`
27955
+ `);
27956
+ setSessionsByKey((prev) => {
27957
+ const next = new Map(prev);
27958
+ const list = next.get(key);
27959
+ if (!list)
27960
+ return prev;
27961
+ const idx = list.findIndex((s) => s.id === id);
27962
+ if (idx === -1)
27963
+ return prev;
27964
+ const updated = [...list];
27965
+ const sess = updated[idx];
27966
+ const newLines = [...sess.lines];
27967
+ for (let i2 = 0;i2 < parts.length; i2++) {
27968
+ const part = parts[i2];
27969
+ if (i2 === 0 && newLines.length > 0) {
27970
+ if (part)
27971
+ newLines[newLines.length - 1] = (newLines[newLines.length - 1] ?? "") + part;
27972
+ } else {
27973
+ if (part || i2 < parts.length - 1)
27974
+ newLines.push(part);
27975
+ }
27976
+ }
27977
+ if (newLines.length > 1000)
27978
+ newLines.splice(0, newLines.length - 1000);
27979
+ updated[idx] = { ...sess, lines: newLines };
27980
+ next.set(key, updated);
27981
+ return next;
27982
+ });
27983
+ };
27984
+ const onPtyData = (data) => {
27985
+ if (data.length > 0) {
27986
+ const text = new TextDecoder().decode(data);
27987
+ if (text.includes("page_list") || text.includes("x icon") || text.includes("received and ignored")) {
27988
+ const reset2 = new TextEncoder().encode("\x1B[0m");
27989
+ const buf2 = rawBuffersRef.current.get(id) ?? [];
27990
+ buf2.push(reset2);
27991
+ if (buf2.length > 2000)
27992
+ buf2.shift();
27993
+ rawBuffersRef.current.set(id, buf2);
27994
+ const listener2 = listenersRef.current.get(id);
27995
+ if (listener2)
27996
+ listener2(reset2);
27997
+ return;
27998
+ }
27999
+ }
28000
+ const buf = rawBuffersRef.current.get(id) ?? [];
28001
+ buf.push(data);
28002
+ if (buf.length > 2000)
28003
+ buf.shift();
28004
+ rawBuffersRef.current.set(id, buf);
28005
+ const listener = listenersRef.current.get(id);
28006
+ if (listener) {
28007
+ listener(data);
28008
+ return;
28009
+ }
28010
+ };
28011
+ const onExit2 = (code) => {
28012
+ setSessionsByKey((prev) => {
28013
+ const next = new Map(prev);
28014
+ const list = next.get(key);
28015
+ if (!list)
28016
+ return prev;
28017
+ const idx = list.findIndex((s) => s.id === id);
28018
+ if (idx === -1)
28019
+ return prev;
28020
+ const updated = [...list];
28021
+ updated[idx] = { ...updated[idx], exited: code ?? 0, proc: null, terminal: null };
28022
+ next.set(key, updated);
28023
+ return next;
28024
+ });
28025
+ procsRef.current.delete(id);
28026
+ listenersRef.current.delete(id);
28027
+ };
28028
+ let usePty = false;
28029
+ try {
28030
+ if (typeof Bun !== "undefined" && typeof Bun.spawn === "function") {
28031
+ try {
28032
+ const proc = Bun.spawn([shell], {
28033
+ cwd: path37 || process.cwd(),
28034
+ env: { ...process.env, TERM: "xterm-256color", COLORTERM: "truecolor", WTX_SESSION: label },
28035
+ terminal: {
28036
+ cols,
28037
+ rows,
28038
+ name: "xterm-256color",
28039
+ data(_terminal, data) {
28040
+ onPtyData(data);
28041
+ },
28042
+ exit(_terminal, exitCode) {
28043
+ onExit2(exitCode);
28044
+ }
28045
+ }
28046
+ });
28047
+ if (proc && proc.terminal) {
28048
+ session.proc = proc;
28049
+ session.terminal = proc.terminal;
28050
+ session.usePty = true;
28051
+ usePty = true;
28052
+ procsRef.current.set(id, proc);
28053
+ (async () => {
28054
+ try {
28055
+ const code = await proc.exited;
28056
+ onExit2(code);
28057
+ } catch {}
28058
+ })();
28059
+ }
28060
+ } catch {}
28061
+ }
28062
+ if (!usePty)
28063
+ throw new Error("pty not available");
28064
+ } catch {
28065
+ try {
28066
+ const proc = Bun.spawn([shell], {
28067
+ cwd: path37 || process.cwd(),
28068
+ stdin: "pipe",
28069
+ stdout: "pipe",
28070
+ stderr: "pipe",
28071
+ env: { ...process.env, TERM: "xterm-256color", WTX_SESSION: label }
28072
+ });
28073
+ session.proc = proc;
28074
+ session.usePty = false;
28075
+ procsRef.current.set(id, proc);
28076
+ (async () => {
28077
+ const stdout = proc.stdout;
28078
+ if (stdout) {
28079
+ const reader = stdout.getReader();
28080
+ try {
28081
+ while (true) {
28082
+ const { value, done } = await reader.read();
28083
+ if (done)
28084
+ break;
28085
+ if (value)
28086
+ appendPipe(value);
28087
+ }
28088
+ } catch {}
28089
+ }
28090
+ })();
28091
+ (async () => {
28092
+ const stderr = proc.stderr;
28093
+ if (stderr) {
28094
+ const reader = stderr.getReader();
28095
+ try {
28096
+ while (true) {
28097
+ const { value, done } = await reader.read();
28098
+ if (done)
28099
+ break;
28100
+ if (value)
28101
+ appendPipe(value);
28102
+ }
28103
+ } catch {}
28104
+ }
28105
+ })();
28106
+ (async () => {
28107
+ try {
28108
+ const code = await proc.exited;
28109
+ onExit2(code);
28110
+ } catch {}
28111
+ })();
28112
+ } catch (e) {
28113
+ session.lines = [`Failed to spawn shell: ${e.message}`];
28114
+ }
28115
+ }
28116
+ if (session.usePty) {
28117
+ session.lines = [`${label} — ${path37} (${shell}) [pty]`, ""];
28118
+ } else if (session.lines.length === 0) {
28119
+ session.lines = [`${label} — ${path37} (${shell})`, ""];
28120
+ }
28121
+ setSessionsByKey((prev) => {
28122
+ const next = new Map(prev);
28123
+ const list = next.get(key) ?? [];
28124
+ next.set(key, [...list, session]);
28125
+ return next;
28126
+ });
28127
+ return { session };
28128
+ }, [sessionsByKey]);
28129
+ const removeSession = useCallback5((repoName, branch, path37, id) => {
28130
+ const key = worktreeKeyFor(repoName, branch, path37);
28131
+ setSessionsByKey((prev) => {
28132
+ const next = new Map(prev);
28133
+ const list = next.get(key);
28134
+ if (!list)
28135
+ return prev;
28136
+ const sess = list.find((s) => s.id === id);
28137
+ if (sess?.proc) {
28138
+ try {
28139
+ if (sess.terminal) {
28140
+ try {
28141
+ sess.terminal.close();
28142
+ } catch {}
28143
+ }
28144
+ sess.proc.kill();
28145
+ } catch {}
28146
+ procsRef.current.delete(id);
28147
+ listenersRef.current.delete(id);
28148
+ rawBuffersRef.current.delete(id);
28149
+ }
28150
+ const filtered = list.filter((s) => s.id !== id);
28151
+ const relabeled = relabelTerminalSessions(filtered);
28152
+ if (relabeled.length === 0) {
28153
+ next.delete(key);
28154
+ } else {
28155
+ next.set(key, relabeled);
28156
+ }
28157
+ return next;
28158
+ });
28159
+ }, []);
28160
+ const sendInput = useCallback5((repoName, branch, path37, id, data) => {
28161
+ const key = worktreeKeyFor(repoName, branch, path37);
28162
+ const list = sessionsByKey.get(key) ?? [];
28163
+ const sess = list.find((s) => s.id === id);
28164
+ if (!sess?.proc)
28165
+ return;
28166
+ try {
28167
+ if (sess.usePty && sess.terminal) {
28168
+ if (typeof data === "string") {
28169
+ sess.terminal.write(new TextEncoder().encode(data));
28170
+ } else {
28171
+ sess.terminal.write(data);
28172
+ }
28173
+ return;
28174
+ }
28175
+ const proc = sess.proc;
28176
+ const str = typeof data === "string" ? data : new TextDecoder().decode(data);
28177
+ if (proc.stdin && typeof proc.stdin.write === "function") {
28178
+ proc.stdin.write(str);
28179
+ } else if (proc.stdin instanceof WritableStream) {
28180
+ const writer = proc.stdin.getWriter();
28181
+ writer.write(new TextEncoder().encode(str));
28182
+ writer.releaseLock();
28183
+ } else {
28184
+ proc.stdin?.write?.(str);
28185
+ }
28186
+ } catch {}
28187
+ if (!sess.usePty) {
28188
+ const str = typeof data === "string" ? data : new TextDecoder().decode(data);
28189
+ if (str.endsWith(`
28190
+ `)) {
28191
+ const cmd = str.slice(0, -1);
28192
+ if (cmd) {
28193
+ setSessionsByKey((prev) => {
28194
+ const next = new Map(prev);
28195
+ const l = next.get(key);
28196
+ if (!l)
28197
+ return prev;
28198
+ const idx = l.findIndex((s2) => s2.id === id);
28199
+ if (idx === -1)
28200
+ return prev;
28201
+ const updated = [...l];
28202
+ const s = updated[idx];
28203
+ updated[idx] = { ...s, lines: [...s.lines, `$ ${cmd}`] };
28204
+ next.set(key, updated);
28205
+ return next;
28206
+ });
28207
+ }
28208
+ }
28209
+ }
28210
+ }, [sessionsByKey]);
28211
+ const resizeSession = useCallback5((repoName, branch, path37, id, cols, rows) => {
28212
+ const key = worktreeKeyFor(repoName, branch, path37);
28213
+ setSessionsByKey((prev) => {
28214
+ const list = prev.get(key);
28215
+ if (!list)
28216
+ return prev;
28217
+ const idx = list.findIndex((s) => s.id === id);
28218
+ if (idx === -1)
28219
+ return prev;
28220
+ const sess = list[idx];
28221
+ if (isSameSize(sess, cols, rows))
28222
+ return prev;
28223
+ try {
28224
+ sess.terminal?.resize?.(cols, rows);
28225
+ } catch {}
28226
+ const next = new Map(prev);
28227
+ const updated = [...list];
28228
+ updated[idx] = updateSessionSize(sess, cols, rows);
28229
+ next.set(key, updated);
28230
+ return next;
28231
+ });
28232
+ }, []);
28233
+ const registerListener = useCallback5((id, fn) => {
28234
+ listenersRef.current.set(id, fn);
28235
+ const buf = rawBuffersRef.current.get(id);
28236
+ if (buf) {
28237
+ for (const chunk of buf) {
28238
+ try {
28239
+ fn(chunk);
28240
+ } catch {}
28241
+ }
28242
+ }
28243
+ }, []);
28244
+ const unregisterListener = useCallback5((id) => {
28245
+ listenersRef.current.delete(id);
28246
+ }, []);
28247
+ const cleanupAll = useCallback5(() => {
28248
+ for (const proc of procsRef.current.values()) {
28249
+ try {
28250
+ if (proc.terminal) {
28251
+ try {
28252
+ proc.terminal.close();
28253
+ } catch {}
28254
+ }
28255
+ proc.kill();
28256
+ } catch {}
28257
+ }
28258
+ procsRef.current.clear();
28259
+ listenersRef.current.clear();
28260
+ rawBuffersRef.current.clear();
28261
+ }, []);
28262
+ useEffect7(() => {
28263
+ return () => cleanupAll();
28264
+ }, [cleanupAll]);
28265
+ const pruneKey = useCallback5((key) => {
28266
+ setSessionsByKey((prev) => {
28267
+ const next = new Map(prev);
28268
+ const list = next.get(key);
28269
+ if (!list)
28270
+ return prev;
28271
+ for (const s of list) {
28272
+ if (s.proc) {
28273
+ try {
28274
+ if (s.terminal) {
28275
+ try {
28276
+ s.terminal.close();
28277
+ } catch {}
28278
+ }
28279
+ s.proc.kill();
28280
+ } catch {}
28281
+ procsRef.current.delete(s.id);
28282
+ listenersRef.current.delete(s.id);
28283
+ rawBuffersRef.current.delete(s.id);
28284
+ }
28285
+ }
28286
+ next.delete(key);
28287
+ return next;
28288
+ });
28289
+ }, []);
28290
+ return { sessionsByKey, getSessions, getKey, createSession, removeSession, sendInput, resizeSession, registerListener, unregisterListener, pruneKey, cleanupAll };
28291
+ }
28292
+ var MAX_TERMINAL_SESSIONS = 5;
28293
+ var init_useTerminalSessions = () => {};
28294
+
27540
28295
  // src/tui/components/App.tsx
27541
- import { useState as useState8, useEffect as useEffect6, useMemo, useRef as useRef5, useCallback as useCallback4 } from "react";
28296
+ import { useState as useState10, useEffect as useEffect8, useMemo, useRef as useRef7, useCallback as useCallback6 } from "react";
27542
28297
  import { existsSync as existsSync4 } from "node:fs";
27543
- import { useKeyboard as useKeyboard3, useRenderer as useRenderer2, useSelectionHandler, useTerminalDimensions } from "@opentui/react";
27544
- import { jsx as jsx14, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
28298
+ import { useKeyboard as useKeyboard3, usePaste, useRenderer as useRenderer2, useSelectionHandler, useTerminalDimensions } from "@opentui/react";
28299
+ import { jsx as jsx17, jsxs as jsxs15 } from "@opentui/react/jsx-runtime";
27545
28300
  function getWorktreePathFor(repoName, branch) {
27546
28301
  try {
27547
28302
  const config2 = loadConfig();
@@ -27554,37 +28309,51 @@ function getWorktreePathFor(repoName, branch) {
27554
28309
  function App({ opts }) {
27555
28310
  const renderer = useRenderer2();
27556
28311
  const { blocks, loading, refreshing, error: error52, warnings, lastRefreshed, pendingRepos, refresh, clearWarnings } = useWorktrees(opts);
27557
- const [selectedIndex, setSelectedIndex] = useState8(0);
27558
- const [modal, setModal] = useState8({ type: "none" });
27559
- const [actionMessage, setActionMessage] = useState8();
27560
- const messageTimer = useRef5(null);
27561
- const [ops, setOps] = useState8([]);
27562
- const [failedLogs, setFailedLogs] = useState8([]);
27563
- const nextOpId = useRef5(1);
27564
- const [createModal, setCreateModal] = useState8(false);
27565
- const [createError, setCreateError] = useState8();
27566
- const [createBaseModal, setCreateBaseModal] = useState8(null);
27567
- const [createBaseError, setCreateBaseError] = useState8();
27568
- const [createDepsChoice, setCreateDepsChoice] = useState8(null);
27569
- const [pullPrModal, setPullPrModal] = useState8(false);
27570
- const [pullPrError, setPullPrError] = useState8();
27571
- const [pullForceChoice, setPullForceChoice] = useState8(null);
27572
- const [renameModal, setRenameModal] = useState8(false);
27573
- const [renameError, setRenameError] = useState8();
27574
- const [configOpen, setConfigOpen] = useState8(false);
27575
- const [refreshScopes, setRefreshScopes] = useState8([]);
27576
- const [filterText, setFilterText] = useState8("");
27577
- const [isFiltering, setIsFiltering] = useState8(false);
27578
- const [selection, setSelection] = useState8(new Set);
27579
- const [splitRatio, setSplitRatio] = useState8(0.6);
27580
- const [isResizing, setIsResizing] = useState8(false);
27581
- const { width: termWidth } = useTerminalDimensions();
27582
- const totalWidth = termWidth || renderer.width || 80;
27583
- useEffect6(() => {
27584
- if (termWidth)
27585
- setSplitRatio((prev) => clampSplitRatio(termWidth, prev));
27586
- }, [termWidth]);
27587
- const doRefresh = useCallback4(async (scope) => {
28312
+ const [selectedIndex, setSelectedIndex] = useState10(0);
28313
+ const [modal, setModal] = useState10({ type: "none" });
28314
+ const [actionMessage, setActionMessage] = useState10();
28315
+ const messageTimer = useRef7(null);
28316
+ const [ops, setOps] = useState10([]);
28317
+ const [failedLogs, setFailedLogs] = useState10([]);
28318
+ const nextOpId = useRef7(1);
28319
+ const [createModal, setCreateModal] = useState10(false);
28320
+ const [createError, setCreateError] = useState10();
28321
+ const [createBaseModal, setCreateBaseModal] = useState10(null);
28322
+ const [createBaseError, setCreateBaseError] = useState10();
28323
+ const [createDepsChoice, setCreateDepsChoice] = useState10(null);
28324
+ const [pullPrModal, setPullPrModal] = useState10(false);
28325
+ const [pullPrError, setPullPrError] = useState10();
28326
+ const [pullForceChoice, setPullForceChoice] = useState10(null);
28327
+ const [renameModal, setRenameModal] = useState10(false);
28328
+ const [renameError, setRenameError] = useState10();
28329
+ const [configOpen, setConfigOpen] = useState10(false);
28330
+ const [refreshScopes, setRefreshScopes] = useState10([]);
28331
+ const [filterText, setFilterText] = useState10("");
28332
+ const [isFiltering, setIsFiltering] = useState10(false);
28333
+ const [selection, setSelection] = useState10(new Set);
28334
+ const [splitRatio, setSplitRatio] = useState10(() => {
28335
+ try {
28336
+ const cfg = loadConfig();
28337
+ const left = cfg.tui?.leftPaneWidthWeight ?? 3;
28338
+ const right = cfg.tui?.rightPaneWidthWeight ?? 7;
28339
+ const total = left + right;
28340
+ if (total > 0)
28341
+ return left / total;
28342
+ } catch {}
28343
+ return 3 / 10;
28344
+ });
28345
+ const [isResizing, setIsResizing] = useState10(false);
28346
+ const terminalSessions = useTerminalSessions();
28347
+ const [activeTabByWorktree, setActiveTabByWorktree] = useState10(() => new Map);
28348
+ const [terminalFocused, setTerminalFocused] = useState10(false);
28349
+ const { width: termW, height: termH } = useTerminalDimensions();
28350
+ const totalWidth = termW || renderer.width || 80;
28351
+ const totalHeight = termH || renderer.height || 24;
28352
+ useEffect8(() => {
28353
+ if (termW)
28354
+ setSplitRatio((prev) => clampSplitRatio(termW, prev));
28355
+ }, [termW]);
28356
+ const doRefresh = useCallback6(async (scope) => {
27588
28357
  const targets = scope ?? [
27589
28358
  ...new Set([...blocks.map((b) => b.repoName), ...pendingRepos])
27590
28359
  ];
@@ -27595,18 +28364,18 @@ function App({ opts }) {
27595
28364
  setRefreshScopes([]);
27596
28365
  }
27597
28366
  }, [blocks, pendingRepos, refresh]);
27598
- useEffect6(() => {
28367
+ useEffect8(() => {
27599
28368
  if (error52) {
27600
28369
  setModal({ type: "error", message: error52 });
27601
28370
  }
27602
28371
  }, [error52]);
27603
- const flash = useCallback4((message, ms = 3000) => {
28372
+ const flash = useCallback6((message, ms = 3000) => {
27604
28373
  if (messageTimer.current)
27605
28374
  clearTimeout(messageTimer.current);
27606
28375
  setActionMessage(message);
27607
28376
  messageTimer.current = setTimeout(() => setActionMessage(undefined), ms);
27608
28377
  }, []);
27609
- const copySelectedText = useCallback4((warnOnEmpty) => {
28378
+ const copySelectedText = useCallback6((warnOnEmpty) => {
27610
28379
  if (isResizing) {
27611
28380
  renderer.clearSelection();
27612
28381
  return;
@@ -27636,12 +28405,139 @@ function App({ opts }) {
27636
28405
  const flatRows = useMemo(() => displayBlocks.flatMap((b) => b.rows.filter((r) => !r.isPendingCreate)), [displayBlocks]);
27637
28406
  const totalRows = blocks.flatMap((b) => b.rows).length;
27638
28407
  const maxIndex = Math.max(0, flatRows.length - 1);
27639
- useEffect6(() => {
28408
+ useEffect8(() => {
27640
28409
  if (selectedIndex > maxIndex && maxIndex >= 0) {
27641
28410
  setSelectedIndex(maxIndex);
27642
28411
  }
27643
28412
  }, [maxIndex, selectedIndex]);
27644
28413
  const selectedRow = flatRows[selectedIndex] ?? null;
28414
+ const worktreeKey = selectedRow ? terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path) : "";
28415
+ const sessionsForSelected = selectedRow ? terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path) : [];
28416
+ const activeTabId = worktreeKey ? activeTabByWorktree.get(worktreeKey) ?? "details" : "details";
28417
+ useEffect8(() => {
28418
+ if (worktreeKey && !activeTabByWorktree.has(worktreeKey)) {
28419
+ setActiveTabByWorktree((prev) => {
28420
+ if (prev.has(worktreeKey))
28421
+ return prev;
28422
+ const next = new Map(prev);
28423
+ next.set(worktreeKey, "details");
28424
+ return next;
28425
+ });
28426
+ }
28427
+ }, [worktreeKey, activeTabByWorktree]);
28428
+ useEffect8(() => {
28429
+ if (sessionsForSelected.length === 0 && activeTabId !== "details") {
28430
+ setActiveTabByWorktree((prev) => {
28431
+ const next = new Map(prev);
28432
+ next.set(worktreeKey, "details");
28433
+ return next;
28434
+ });
28435
+ setTerminalFocused(false);
28436
+ } else if (sessionsForSelected.length > 0) {
28437
+ const exists = sessionsForSelected.some((s) => s.id === activeTabId) || activeTabId === "details";
28438
+ if (!exists) {
28439
+ setActiveTabByWorktree((prev) => {
28440
+ const next = new Map(prev);
28441
+ next.set(worktreeKey, sessionsForSelected[0].id);
28442
+ return next;
28443
+ });
28444
+ }
28445
+ }
28446
+ }, [sessionsForSelected, activeTabId, worktreeKey]);
28447
+ usePaste((event) => {
28448
+ if (!terminalFocused)
28449
+ return;
28450
+ const activeSession = sessionsForSelected.find((s) => s.id === activeTabId);
28451
+ if (!activeSession)
28452
+ return;
28453
+ event.preventDefault();
28454
+ const data = event.bytes;
28455
+ if (data.length === 0)
28456
+ return;
28457
+ terminalSessions.sendInput(activeSession.repoName, activeSession.branch, activeSession.worktreePath, activeSession.id, data);
28458
+ });
28459
+ const handleSelectTab = useCallback6((id) => {
28460
+ if (!worktreeKey)
28461
+ return;
28462
+ setActiveTabByWorktree((prev) => {
28463
+ const next = new Map(prev);
28464
+ next.set(worktreeKey, id);
28465
+ return next;
28466
+ });
28467
+ const isSession = sessionsForSelected.some((s) => s.id === id);
28468
+ setTerminalFocused(isSession);
28469
+ }, [worktreeKey, sessionsForSelected]);
28470
+ const handleAddSession = useCallback6(() => {
28471
+ if (!selectedRow) {
28472
+ flash("No worktree selected");
28473
+ return;
28474
+ }
28475
+ const rawLeftTmp = Math.floor(totalWidth * splitRatio);
28476
+ const leftColsTmp = Math.max(20, Math.min(totalWidth - 20 - DIVIDER_WIDTH, rawLeftTmp));
28477
+ const rightColsTmp = Math.max(20, totalWidth - leftColsTmp - DIVIDER_WIDTH);
28478
+ const cols = Math.max(20, rightColsTmp - 4);
28479
+ const rows = Math.max(10, totalHeight - 10);
28480
+ const { session, error: error53 } = terminalSessions.createSession(selectedRow.repoName, selectedRow.branch, selectedRow.path, { cols, rows });
28481
+ if (error53 || !session) {
28482
+ flash(error53 ?? "Failed to create session");
28483
+ return;
28484
+ }
28485
+ const key = terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28486
+ setActiveTabByWorktree((prev) => {
28487
+ const next = new Map(prev);
28488
+ next.set(key, session.id);
28489
+ return next;
28490
+ });
28491
+ setTerminalFocused(true);
28492
+ flash(`Session ${session.label} created`, 2000);
28493
+ }, [selectedRow, terminalSessions, flash, totalWidth, splitRatio, totalHeight]);
28494
+ const handleCloseSession = useCallback6((id) => {
28495
+ if (!selectedRow)
28496
+ return;
28497
+ terminalSessions.removeSession(selectedRow.repoName, selectedRow.branch, selectedRow.path, id);
28498
+ const key = terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28499
+ setActiveTabByWorktree((prev) => {
28500
+ const next = new Map(prev);
28501
+ const current = next.get(key);
28502
+ if (current === id)
28503
+ next.set(key, "details");
28504
+ return next;
28505
+ });
28506
+ setTerminalFocused(false);
28507
+ flash("Session closed", 2000);
28508
+ }, [selectedRow, terminalSessions, flash]);
28509
+ useEffect8(() => {
28510
+ const existingKeys = new Set(blocks.flatMap((b) => b.rows.map((r) => terminalSessions.getKey(r.repoName, r.branch, r.path))));
28511
+ for (const key of terminalSessions.sessionsByKey.keys()) {
28512
+ if (!existingKeys.has(key))
28513
+ terminalSessions.pruneKey(key);
28514
+ }
28515
+ }, [blocks, terminalSessions]);
28516
+ const allSessionsFlat = useMemo(() => Array.from(terminalSessions.sessionsByKey.values()).flat(), [terminalSessions.sessionsByKey]);
28517
+ const tabsForSelected = useMemo(() => {
28518
+ const base2 = [
28519
+ {
28520
+ id: "details",
28521
+ label: "Details",
28522
+ render: ({ worktree }) => /* @__PURE__ */ jsx17(DetailsContent, {
28523
+ selectedRow: worktree
28524
+ })
28525
+ }
28526
+ ];
28527
+ for (const s of sessionsForSelected) {
28528
+ base2.push({
28529
+ id: s.id,
28530
+ label: s.label,
28531
+ closable: true,
28532
+ render: () => /* @__PURE__ */ jsx17("box", {
28533
+ flexGrow: 1,
28534
+ width: "100%",
28535
+ height: "100%"
28536
+ })
28537
+ });
28538
+ }
28539
+ return base2;
28540
+ }, [sessionsForSelected]);
27645
28541
  const { repoVerbs, rowVerbs } = useMemo(() => {
27646
28542
  const rv = new Map;
27647
28543
  const nv = new Map;
@@ -27780,7 +28676,7 @@ function App({ opts }) {
27780
28676
  args.push("--force");
27781
28677
  executeOp(op, args, [repoName]);
27782
28678
  };
27783
- const handleHintClick = useCallback4((key) => {
28679
+ const handleHintClick = useCallback6((key) => {
27784
28680
  if (key === "c") {
27785
28681
  setConfigOpen(true);
27786
28682
  return;
@@ -27932,8 +28828,79 @@ function App({ opts }) {
27932
28828
  setModal({ type: "confirm_sync", rows: targets });
27933
28829
  return;
27934
28830
  }
27935
- }, [selectedRow, selection, warnings, busyRepos, busyRowPaths, doRefresh, flash, blocks]);
28831
+ if (key === "t") {
28832
+ if (!selectedRow)
28833
+ return;
28834
+ const sessions = terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28835
+ if (sessions.length >= MAX_TERMINAL_SESSIONS) {
28836
+ flash(`Max ${MAX_TERMINAL_SESSIONS} sessions per worktree`);
28837
+ return;
28838
+ }
28839
+ handleAddSession();
28840
+ return;
28841
+ }
28842
+ }, [selectedRow, selection, warnings, busyRepos, busyRowPaths, doRefresh, flash, blocks, terminalSessions]);
27936
28843
  useKeyboard3((key) => {
28844
+ if (terminalFocused) {
28845
+ if (key.ctrl && key.name === "g") {
28846
+ setTerminalFocused(false);
28847
+ return;
28848
+ }
28849
+ const activeSession = sessionsForSelected.find((s) => s.id === activeTabId);
28850
+ if (activeSession) {
28851
+ if ((key.ctrl || key.super || key.meta) && key.name === "v") {
28852
+ readTextFromClipboard().then((text) => {
28853
+ if (text)
28854
+ terminalSessions.sendInput(activeSession.repoName, activeSession.branch, activeSession.worktreePath, activeSession.id, text);
28855
+ });
28856
+ return;
28857
+ }
28858
+ let data = null;
28859
+ const seq = key.sequence;
28860
+ const raw = key.raw;
28861
+ if (key.ctrl && key.name && key.name.length === 1) {
28862
+ const code = key.name.charCodeAt(0) - 96;
28863
+ if (code >= 1 && code <= 26)
28864
+ data = String.fromCharCode(code);
28865
+ else if (seq)
28866
+ data = seq;
28867
+ else if (raw)
28868
+ data = raw;
28869
+ } else if (seq) {
28870
+ data = seq;
28871
+ } else if (raw) {
28872
+ data = raw;
28873
+ } else if (key.name === "return" || key.name === "enter") {
28874
+ data = "\r";
28875
+ } else if (key.name === "backspace") {
28876
+ data = "";
28877
+ } else if (key.name === "tab") {
28878
+ data = "\t";
28879
+ } else if (key.name === "escape") {
28880
+ data = "\x1B";
28881
+ } else if (key.name && key.name.length === 1) {
28882
+ data = key.name;
28883
+ }
28884
+ if (data !== null) {
28885
+ terminalSessions.sendInput(activeSession.repoName, activeSession.branch, activeSession.worktreePath, activeSession.id, data);
28886
+ }
28887
+ }
28888
+ return;
28889
+ }
28890
+ if (key.ctrl && key.name === "g") {
28891
+ if (sessionsForSelected.length > 0) {
28892
+ const targetId = activeTabId !== "details" ? activeTabId : sessionsForSelected[0].id;
28893
+ if (worktreeKey) {
28894
+ setActiveTabByWorktree((prev) => {
28895
+ const next = new Map(prev);
28896
+ next.set(worktreeKey, targetId);
28897
+ return next;
28898
+ });
28899
+ }
28900
+ setTerminalFocused(true);
28901
+ return;
28902
+ }
28903
+ }
27937
28904
  if (isFiltering) {
27938
28905
  if (key.name === "escape") {
27939
28906
  setIsFiltering(false);
@@ -28245,108 +29212,110 @@ function App({ opts }) {
28245
29212
  setModal({ type: "confirm_sync", rows: targets });
28246
29213
  return;
28247
29214
  }
28248
- if (key.name === "a") {
28249
- const targets = getSelectedRows();
28250
- if (targets.length === 0)
29215
+ if (key.name === "t") {
29216
+ if (!selectedRow) {
29217
+ flash("No worktree selected");
28251
29218
  return;
28252
- const target = targets[0];
28253
- if (!target)
28254
- return;
28255
- const conflict = findConflict([target]);
28256
- if (conflict) {
28257
- flash(conflict);
29219
+ }
29220
+ const sessions = terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path);
29221
+ if (sessions.length >= MAX_TERMINAL_SESSIONS) {
29222
+ flash(`Max ${MAX_TERMINAL_SESSIONS} sessions per worktree`);
28258
29223
  return;
28259
29224
  }
28260
- (async () => {
28261
- const startedAt = Date.now();
28262
- try {
28263
- const config2 = await loadConfig();
28264
- const cmdTemplate = resolveAgentCommand("claude", config2.agents) ?? "claude";
28265
- const result = await spawnAgentInWorktree(cmdTemplate, target.path, { repoName: target.repoName, branch: target.branch });
28266
- appendHistory({
28267
- ts: new Date().toISOString(),
28268
- source: "terminal",
28269
- command: "agent",
28270
- args: ["agent", target.branch, "--repo", target.repoName],
28271
- durationMs: Date.now() - startedAt,
28272
- exit: 0
28273
- });
28274
- flash(`Agent spawned (${result.mode}${result.session ? ` session ${result.session}` : ""})`, 5000);
28275
- } catch (e) {
28276
- appendHistory({
28277
- ts: new Date().toISOString(),
28278
- source: "terminal",
28279
- command: "agent",
28280
- args: ["agent", target.branch, "--repo", target.repoName],
28281
- durationMs: Date.now() - startedAt,
28282
- exit: 1
28283
- });
28284
- flash(`Agent failed: ${e.message}`, 5000);
28285
- }
28286
- })();
29225
+ handleAddSession();
29226
+ return;
28287
29227
  }
28288
29228
  });
28289
29229
  const rawLeft = Math.floor(totalWidth * splitRatio);
28290
29230
  const leftCols = Math.max(20, Math.min(totalWidth - 20 - DIVIDER_WIDTH, rawLeft));
28291
29231
  const rightCols = Math.max(20, totalWidth - leftCols - DIVIDER_WIDTH);
28292
- return /* @__PURE__ */ jsxs12("box", {
29232
+ const paneCols = Math.max(20, rightCols - 4);
29233
+ const paneRows = Math.max(10, totalHeight - 10);
29234
+ useEffect8(() => {
29235
+ for (const sessions of terminalSessions.sessionsByKey.values()) {
29236
+ for (const s of sessions) {
29237
+ if (s.usePty && s.terminal && (s.cols !== paneCols || s.rows !== paneRows)) {
29238
+ terminalSessions.resizeSession(s.repoName, s.branch, s.worktreePath, s.id, paneCols, paneRows);
29239
+ }
29240
+ }
29241
+ }
29242
+ }, [paneCols, paneRows, terminalSessions]);
29243
+ useEffect8(() => {
29244
+ setTerminalFocused(false);
29245
+ }, [selectedIndex]);
29246
+ return /* @__PURE__ */ jsxs15("box", {
28293
29247
  flexDirection: "column",
28294
29248
  width: "100%",
28295
29249
  height: "100%",
28296
29250
  children: [
28297
- /* @__PURE__ */ jsxs12("box", {
29251
+ /* @__PURE__ */ jsxs15("box", {
28298
29252
  flexDirection: "row",
28299
29253
  width: "100%",
28300
29254
  flexGrow: 1,
28301
29255
  children: [
28302
- /* @__PURE__ */ jsx14("box", {
29256
+ /* @__PURE__ */ jsx17("box", {
28303
29257
  width: leftCols,
28304
29258
  height: "100%",
28305
29259
  flexDirection: "column",
28306
- children: /* @__PURE__ */ jsx14(WorktreeTable, {
29260
+ onMouseDown: () => setTerminalFocused(false),
29261
+ children: /* @__PURE__ */ jsx17(WorktreeTable, {
28307
29262
  blocks: displayBlocks,
28308
29263
  selectedIndex,
28309
29264
  selection,
28310
29265
  frame: spinnerFrame,
28311
29266
  repoVerbs,
28312
29267
  rowVerbs,
28313
- onRowClick: (idx) => setSelectedIndex(idx),
29268
+ onRowClick: (idx) => {
29269
+ setTerminalFocused(false);
29270
+ setSelectedIndex(idx);
29271
+ },
28314
29272
  onToggleSelect: (path37) => setSelection((prev) => toggleSelection(prev, path37))
28315
29273
  })
28316
29274
  }),
28317
- /* @__PURE__ */ jsx14(Divider, {
29275
+ /* @__PURE__ */ jsx17(Divider, {
28318
29276
  splitRatio,
28319
29277
  totalWidth,
28320
29278
  onChange: setSplitRatio,
28321
29279
  onDraggingChange: setIsResizing
28322
29280
  }),
28323
- /* @__PURE__ */ jsx14("box", {
29281
+ /* @__PURE__ */ jsx17("box", {
28324
29282
  width: rightCols,
28325
29283
  height: "100%",
28326
29284
  flexDirection: "column",
28327
- children: /* @__PURE__ */ jsx14(DetailPane, {
28328
- selectedRow
29285
+ children: /* @__PURE__ */ jsx17(TabPane, {
29286
+ selectedRow,
29287
+ tabs: tabsForSelected,
29288
+ activeId: activeTabId,
29289
+ focused: terminalFocused && tabsForSelected.some((t) => t.id === activeTabId && t.id !== "details"),
29290
+ canAdd: sessionsForSelected.length < MAX_TERMINAL_SESSIONS,
29291
+ onSelect: handleSelectTab,
29292
+ onAdd: handleAddSession,
29293
+ onClose: handleCloseSession,
29294
+ allSessionsFlat,
29295
+ terminalFocused,
29296
+ activeTabId,
29297
+ terminalSessions
28329
29298
  })
28330
29299
  })
28331
29300
  ]
28332
29301
  }),
28333
- isFiltering && /* @__PURE__ */ jsxs12("box", {
29302
+ isFiltering && /* @__PURE__ */ jsxs15("box", {
28334
29303
  flexDirection: "row",
28335
29304
  paddingX: 1,
28336
29305
  border: true,
28337
29306
  borderColor: "magenta",
28338
29307
  children: [
28339
- /* @__PURE__ */ jsx14("text", {
29308
+ /* @__PURE__ */ jsx17("text", {
28340
29309
  children: "filter: "
28341
29310
  }),
28342
- /* @__PURE__ */ jsx14("input", {
29311
+ /* @__PURE__ */ jsx17("input", {
28343
29312
  focused: true,
28344
29313
  placeholder: "Type to filter...",
28345
29314
  onInput: (v) => setFilterText(v)
28346
29315
  })
28347
29316
  ]
28348
29317
  }),
28349
- /* @__PURE__ */ jsx14(Footer, {
29318
+ /* @__PURE__ */ jsx17(Footer, {
28350
29319
  loading: loading || refreshing,
28351
29320
  lastRefreshed,
28352
29321
  errorCount: warnings.length,
@@ -28357,9 +29326,9 @@ function App({ opts }) {
28357
29326
  onHintClick: handleHintClick,
28358
29327
  onErrorClick: () => setModal({ type: "warnings" })
28359
29328
  }),
28360
- modal.type === "help" && /* @__PURE__ */ jsx14(HelpOverlay, {}),
28361
- modal.type === "history" && /* @__PURE__ */ jsx14(HistoryOverlay, {}),
28362
- modal.type === "warnings" && /* @__PURE__ */ jsx14(WarningsOverlay, {
29329
+ modal.type === "help" && /* @__PURE__ */ jsx17(HelpOverlay, {}),
29330
+ modal.type === "history" && /* @__PURE__ */ jsx17(HistoryOverlay, {}),
29331
+ modal.type === "warnings" && /* @__PURE__ */ jsx17(WarningsOverlay, {
28363
29332
  warnings,
28364
29333
  onAcknowledge: () => {
28365
29334
  clearWarnings();
@@ -28367,7 +29336,7 @@ function App({ opts }) {
28367
29336
  },
28368
29337
  onClose: () => setModal({ type: "none" })
28369
29338
  }),
28370
- modal.type === "error" && /* @__PURE__ */ jsx14(ConfirmModal, {
29339
+ modal.type === "error" && /* @__PURE__ */ jsx17(ConfirmModal, {
28371
29340
  title: "Error",
28372
29341
  message: modal.message,
28373
29342
  onConfirm: () => {
@@ -28385,7 +29354,7 @@ function App({ opts }) {
28385
29354
  }
28386
29355
  }
28387
29356
  }),
28388
- modal.type === "confirm_remove" && /* @__PURE__ */ jsx14(ConfirmModal, {
29357
+ modal.type === "confirm_remove" && /* @__PURE__ */ jsx17(ConfirmModal, {
28389
29358
  title: `Remove ${modal.rows.length} Worktree(s)`,
28390
29359
  message: (() => {
28391
29360
  const count2 = modal.rows.length;
@@ -28414,7 +29383,7 @@ function App({ opts }) {
28414
29383
  },
28415
29384
  onCancel: () => setModal({ type: "none" })
28416
29385
  }),
28417
- modal.type === "confirm_rebase" && /* @__PURE__ */ jsx14(ConfirmModal, {
29386
+ modal.type === "confirm_rebase" && /* @__PURE__ */ jsx17(ConfirmModal, {
28418
29387
  title: `Rebase ${modal.rows.length} Worktree(s)`,
28419
29388
  message: `Are you sure you want to fetch and rebase ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`,
28420
29389
  onConfirm: () => {
@@ -28424,7 +29393,7 @@ function App({ opts }) {
28424
29393
  },
28425
29394
  onCancel: () => setModal({ type: "none" })
28426
29395
  }),
28427
- modal.type === "confirm_sync" && /* @__PURE__ */ jsx14(ConfirmModal, {
29396
+ modal.type === "confirm_sync" && /* @__PURE__ */ jsx17(ConfirmModal, {
28428
29397
  title: `Sync ${modal.rows.length} Worktree(s)`,
28429
29398
  message: `Are you sure you want to sync ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`,
28430
29399
  onConfirm: () => {
@@ -28434,7 +29403,7 @@ function App({ opts }) {
28434
29403
  },
28435
29404
  onCancel: () => setModal({ type: "none" })
28436
29405
  }),
28437
- createModal && /* @__PURE__ */ jsx14(InputModal, {
29406
+ createModal && /* @__PURE__ */ jsx17(InputModal, {
28438
29407
  title: `New worktree branch in ${selectedRow?.repoName ?? ""}`,
28439
29408
  placeholder: "Branch name (empty to cancel)",
28440
29409
  errorMessage: createError,
@@ -28461,7 +29430,7 @@ function App({ opts }) {
28461
29430
  setCreateBaseModal({ branch, repoName });
28462
29431
  }
28463
29432
  }),
28464
- createBaseModal && /* @__PURE__ */ jsx14(InputModal, {
29433
+ createBaseModal && /* @__PURE__ */ jsx17(InputModal, {
28465
29434
  title: `Base ref for ${createBaseModal.branch}`,
28466
29435
  placeholder: "origin/main (empty for default main)",
28467
29436
  errorMessage: createBaseError,
@@ -28477,7 +29446,7 @@ function App({ opts }) {
28477
29446
  setCreateDepsChoice({ branch, repoName, base: base2 || undefined });
28478
29447
  }
28479
29448
  }),
28480
- createDepsChoice && /* @__PURE__ */ jsx14(ChoiceModal, {
29449
+ createDepsChoice && /* @__PURE__ */ jsx17(ChoiceModal, {
28481
29450
  title: `Dependencies for ${createDepsChoice.branch}`,
28482
29451
  options: DEPS_CHOICES,
28483
29452
  onSubmit: (choice) => {
@@ -28487,7 +29456,7 @@ function App({ opts }) {
28487
29456
  },
28488
29457
  onCancel: () => setCreateDepsChoice(null)
28489
29458
  }),
28490
- pullPrModal && /* @__PURE__ */ jsx14(InputModal, {
29459
+ pullPrModal && /* @__PURE__ */ jsx17(InputModal, {
28491
29460
  title: `Pull PR into ${selectedRow?.repoName ?? ""}`,
28492
29461
  placeholder: "https://github.com/owner/repo/pull/123",
28493
29462
  errorMessage: pullPrError,
@@ -28515,7 +29484,7 @@ function App({ opts }) {
28515
29484
  setPullForceChoice({ link, repoName });
28516
29485
  }
28517
29486
  }),
28518
- pullForceChoice && /* @__PURE__ */ jsx14(ChoiceModal, {
29487
+ pullForceChoice && /* @__PURE__ */ jsx17(ChoiceModal, {
28519
29488
  title: `Pull ${pullForceChoice.link.split("/").pop()}?`,
28520
29489
  options: [
28521
29490
  { value: "normal", label: "Pull (skip if exists)", desc: "Fails if branch already exists" },
@@ -28528,7 +29497,7 @@ function App({ opts }) {
28528
29497
  },
28529
29498
  onCancel: () => setPullForceChoice(null)
28530
29499
  }),
28531
- renameModal && selectedRow && /* @__PURE__ */ jsx14(InputModal, {
29500
+ renameModal && selectedRow && /* @__PURE__ */ jsx17(InputModal, {
28532
29501
  title: `Rename branch ${selectedRow.branch}`,
28533
29502
  initialValue: selectedRow.branch,
28534
29503
  placeholder: "New branch name",
@@ -28553,7 +29522,7 @@ function App({ opts }) {
28553
29522
  setModal({ type: "confirm_rename", row: target, to });
28554
29523
  }
28555
29524
  }),
28556
- modal.type === "confirm_rename" && /* @__PURE__ */ jsx14(ConfirmModal, {
29525
+ modal.type === "confirm_rename" && /* @__PURE__ */ jsx17(ConfirmModal, {
28557
29526
  title: "Rename Worktree",
28558
29527
  message: [
28559
29528
  `${modal.row.branch} → ${modal.to}`,
@@ -28584,12 +29553,12 @@ function App({ opts }) {
28584
29553
  },
28585
29554
  onCancel: () => setModal({ type: "none" })
28586
29555
  }),
28587
- configOpen && /* @__PURE__ */ jsx14(ConfigOverlay, {
29556
+ configOpen && /* @__PURE__ */ jsx17(ConfigOverlay, {
28588
29557
  onClose: () => setConfigOpen(false),
28589
29558
  onSaved: () => void doRefresh(),
28590
29559
  onError: (msg) => setModal({ type: "error", message: msg })
28591
29560
  }),
28592
- failedLogs.length > 0 && /* @__PURE__ */ jsx14(ActionLogModal, {
29561
+ failedLogs.length > 0 && /* @__PURE__ */ jsx17(ActionLogModal, {
28593
29562
  title: failedLogs[0].title,
28594
29563
  lines: failedLogs[0].lines,
28595
29564
  done: true,
@@ -28603,7 +29572,8 @@ var VERBS, capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1), DEPS_CHOI
28603
29572
  var init_App = __esm(() => {
28604
29573
  init_useWorktrees();
28605
29574
  init_WorktreeTable();
28606
- init_DetailPane();
29575
+ init_TabPane();
29576
+ init_DetailsTab();
28607
29577
  init_Footer();
28608
29578
  init_Divider();
28609
29579
  init_HelpOverlay();
@@ -28616,10 +29586,9 @@ var init_App = __esm(() => {
28616
29586
  init_ConfigOverlay();
28617
29587
  init_WarningsOverlay();
28618
29588
  init_utils();
28619
- init_agents();
28620
29589
  init_config();
28621
- init_history();
28622
29590
  init_useSpinnerFrame();
29591
+ init_useTerminalSessions();
28623
29592
  VERBS = {
28624
29593
  create: "creating worktree",
28625
29594
  remove: "deleting",
@@ -28643,27 +29612,103 @@ var exports_tui = {};
28643
29612
  __export(exports_tui, {
28644
29613
  runTerminal: () => runTerminal
28645
29614
  });
28646
- import { createCliRenderer, TextTableRenderable } from "@opentui/core";
29615
+ import { createCliRenderer, TextTableRenderable, EmbeddedTerminalRenderable } from "@opentui/core";
28647
29616
  import { createRoot, extend as extend2 } from "@opentui/react";
28648
- import { jsx as jsx15 } from "@opentui/react/jsx-runtime";
29617
+ import { jsx as jsx18 } from "@opentui/react/jsx-runtime";
28649
29618
  async function runTerminal(opts) {
29619
+ setQuiet(true);
29620
+ const prevLog = console.log;
29621
+ const prevInfo = console.info;
29622
+ const prevWarn = console.warn;
29623
+ const prevError = console.error;
29624
+ const prevStdoutWrite = process.stdout.write.bind(process.stdout);
29625
+ const prevStderrWrite = process.stderr.write.bind(process.stderr);
29626
+ const shouldSuppress = (chunk) => {
29627
+ const s = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? new TextDecoder().decode(chunk) : String(chunk);
29628
+ return s.includes("page_list") || s.includes("adjusting page") || s.includes("x icon") || s.includes("received and ignored");
29629
+ };
29630
+ const suppressed = (..._args) => {};
29631
+ const filteredError = (...args) => {
29632
+ if (args.some(shouldSuppress))
29633
+ return;
29634
+ prevError(...args);
29635
+ };
29636
+ console.log = suppressed;
29637
+ console.info = suppressed;
29638
+ console.warn = suppressed;
29639
+ console.error = filteredError;
29640
+ process.stdout.write = (chunk, ...rest) => {
29641
+ if (shouldSuppress(chunk))
29642
+ return true;
29643
+ return prevStdoutWrite(chunk, ...rest);
29644
+ };
29645
+ process.stderr.write = (chunk, ...rest) => {
29646
+ if (shouldSuppress(chunk))
29647
+ return true;
29648
+ return prevStderrWrite(chunk, ...rest);
29649
+ };
29650
+ let nativeStderrSaved = null;
29651
+ let nativeLib = null;
29652
+ try {
29653
+ const { dlopen, FFIType } = await import("bun:ffi");
29654
+ nativeLib = dlopen("/usr/lib/libSystem.B.dylib", {
29655
+ dup: { args: [FFIType.i32], returns: FFIType.i32 },
29656
+ dup2: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
29657
+ open: { args: [FFIType.cstring, FFIType.i32], returns: FFIType.i32 },
29658
+ close: { args: [FFIType.i32], returns: FFIType.i32 }
29659
+ });
29660
+ const nullFd = nativeLib.symbols.open("/dev/null", 1);
29661
+ if (nullFd >= 0) {
29662
+ const saved = nativeLib.symbols.dup(2);
29663
+ if (saved >= 0) {
29664
+ nativeStderrSaved = saved;
29665
+ nativeLib.symbols.dup2(nullFd, 2);
29666
+ }
29667
+ nativeLib.symbols.close(nullFd);
29668
+ }
29669
+ } catch {}
28650
29670
  const renderer = await createCliRenderer({
28651
- exitOnCtrlC: false
28652
- });
29671
+ exitOnCtrlC: false,
29672
+ consoleMode: "disabled"
29673
+ });
29674
+ const restoreConsole = () => {
29675
+ console.log = prevLog;
29676
+ console.info = prevInfo;
29677
+ console.warn = prevWarn;
29678
+ console.error = prevError;
29679
+ process.stdout.write = prevStdoutWrite;
29680
+ process.stderr.write = prevStderrWrite;
29681
+ if (nativeStderrSaved !== null && nativeStderrSaved >= 0 && nativeLib) {
29682
+ try {
29683
+ nativeLib.symbols.dup2(nativeStderrSaved, 2);
29684
+ nativeLib.symbols.close(nativeStderrSaved);
29685
+ } catch {}
29686
+ nativeStderrSaved = null;
29687
+ }
29688
+ setQuiet(false);
29689
+ };
28653
29690
  const root = createRoot(renderer);
28654
29691
  try {
28655
- root.render(/* @__PURE__ */ jsx15(App, {
29692
+ root.render(/* @__PURE__ */ jsx18(App, {
28656
29693
  opts
28657
29694
  }));
28658
29695
  } catch (err) {
29696
+ restoreConsole();
28659
29697
  renderer.destroy();
28660
29698
  console.error("Failed to render TUI:", err);
28661
29699
  process.exit(1);
28662
29700
  }
29701
+ const originalDestroy = renderer.destroy.bind(renderer);
29702
+ renderer.destroy = () => {
29703
+ restoreConsole();
29704
+ return originalDestroy();
29705
+ };
28663
29706
  }
28664
29707
  var init_tui = __esm(() => {
29708
+ init_env();
28665
29709
  init_App();
28666
- extend2({ "text-table": TextTableRenderable });
29710
+ init_log();
29711
+ extend2({ "text-table": TextTableRenderable, "embedded-terminal": EmbeddedTerminalRenderable });
28667
29712
  });
28668
29713
 
28669
29714
  // node_modules/commander/lib/error.js
@@ -31002,7 +32047,80 @@ async function runPostCreateSetup(params) {
31002
32047
  init_deps();
31003
32048
  init_forge();
31004
32049
  init_owner();
31005
- init_agents();
32050
+
32051
+ // src/lib/agents.ts
32052
+ init_execa();
32053
+ var DEFAULT_AGENTS = {
32054
+ claude: "claude",
32055
+ codex: "codex",
32056
+ opencode: "opencode",
32057
+ cursor: "cursor"
32058
+ };
32059
+ function resolveAgentCommand(name, configured) {
32060
+ return configured?.[name]?.command ?? DEFAULT_AGENTS[name] ?? null;
32061
+ }
32062
+ function listAvailableAgents(configured) {
32063
+ return [...new Set([...Object.keys(DEFAULT_AGENTS), ...Object.keys(configured ?? {})])];
32064
+ }
32065
+ var tmuxAvailable = undefined;
32066
+ async function isTmuxAvailable() {
32067
+ if (tmuxAvailable !== undefined)
32068
+ return tmuxAvailable;
32069
+ try {
32070
+ await execa("tmux", ["-V"]);
32071
+ tmuxAvailable = true;
32072
+ } catch {
32073
+ tmuxAvailable = false;
32074
+ }
32075
+ return tmuxAvailable;
32076
+ }
32077
+ function tmuxSessionName(repoName, branch) {
32078
+ const sanitized = `${repoName}-${branch}`.replace(/[\/.:]/g, "-");
32079
+ return `wtx-${sanitized}`.substring(0, 60);
32080
+ }
32081
+ function buildTmuxArgs(session, wtPath, cmd) {
32082
+ return ["new-session", "-d", "-s", session, "-c", wtPath, cmd];
32083
+ }
32084
+ async function spawnAgentInWorktree(commandTemplate, wtPath, opts = {}) {
32085
+ const cmd = buildAgentCommand(commandTemplate, wtPath, opts.prompt, {
32086
+ branch: opts.branch,
32087
+ repo: opts.repoName
32088
+ });
32089
+ const session = opts.repoName && opts.branch ? tmuxSessionName(opts.repoName, opts.branch) : undefined;
32090
+ if (opts.dryRun) {
32091
+ if (session && await isTmuxAvailable()) {
32092
+ return { mode: "tmux", session };
32093
+ }
32094
+ return { mode: "direct" };
32095
+ }
32096
+ if (session && await isTmuxAvailable()) {
32097
+ try {
32098
+ const args = buildTmuxArgs(session, wtPath, cmd);
32099
+ await execa("tmux", args, { stdio: "inherit" });
32100
+ return { mode: "tmux", session };
32101
+ } catch {}
32102
+ }
32103
+ await execa(cmd, { shell: true, cwd: wtPath, stdio: "inherit" });
32104
+ return { mode: "direct" };
32105
+ }
32106
+ function shellQuote(value) {
32107
+ return `'${value.replaceAll("'", `'''`)}'`;
32108
+ }
32109
+ function buildAgentCommand(commandTemplate, wtPath, prompt, vars) {
32110
+ let cmd = commandTemplate.replaceAll("{wt}", shellQuote(wtPath));
32111
+ if (vars?.branch) {
32112
+ cmd = cmd.replaceAll("{branch}", shellQuote(vars.branch));
32113
+ }
32114
+ if (vars?.repo) {
32115
+ cmd = cmd.replaceAll("{repo}", shellQuote(vars.repo));
32116
+ }
32117
+ if (prompt) {
32118
+ cmd += ` ${shellQuote(prompt)}`;
32119
+ }
32120
+ return cmd;
32121
+ }
32122
+
32123
+ // src/commands/create.ts
31006
32124
  var DEPS_STRATEGIES = ["auto", "link", "symlink", "install", "off"];
31007
32125
  async function applyDepsStrategy(strategy, wtPath, mainPath, opts) {
31008
32126
  if (strategy === "install") {
@@ -33565,6 +34683,10 @@ function registerTerminalCommand(program2) {
33565
34683
  `);
33566
34684
  process.exit(1);
33567
34685
  }
34686
+ process.env.RUST_LOG ??= "off";
34687
+ process.env.ZIG_LOG ??= "off";
34688
+ process.env.PAGE_LIST_LOG ??= "off";
34689
+ process.env.OPENTUI_LOG ??= "off";
33568
34690
  const { runTerminal: runTerminal2 } = await Promise.resolve().then(() => (init_tui(), exports_tui));
33569
34691
  await runTerminal2(opts);
33570
34692
  });