@ogpoyraz/wtx 0.8.8 → 0.8.9

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 +1273 -416
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -14672,7 +14672,7 @@ function getVersion() {
14672
14672
  return "0.0.0";
14673
14673
  }
14674
14674
  }
14675
- var DepsManagerEnum, DepsStrategyEnum, DepsConfigSchema, AgentConfigSchema, PortsConfigSchema, LEGACY_REPO_KEYS, RepoConfigSchema, ConfigSchema, VERSION;
14675
+ var DepsManagerEnum, DepsStrategyEnum, DepsConfigSchema, AgentConfigSchema, PortsConfigSchema, TuiConfigSchema, LEGACY_REPO_KEYS, RepoConfigSchema, ConfigSchema, VERSION;
14676
14676
  var init_types = __esm(() => {
14677
14677
  init_v4();
14678
14678
  DepsManagerEnum = exports_external.enum(["auto", "npm", "bun", "pnpm", "yarn", "go", "python", "cargo"]);
@@ -14688,6 +14688,10 @@ var init_types = __esm(() => {
14688
14688
  min: exports_external.number().int().min(1024).max(65535).default(4100),
14689
14689
  max: exports_external.number().int().min(1024).max(65535).default(4999)
14690
14690
  }).refine((v) => v.min <= v.max, { message: "ports.min must be less than or equal to ports.max" });
14691
+ TuiConfigSchema = exports_external.object({
14692
+ leftPaneWidthWeight: exports_external.number().int().min(1).max(10).default(3),
14693
+ rightPaneWidthWeight: exports_external.number().int().min(1).max(10).default(7)
14694
+ });
14691
14695
  LEGACY_REPO_KEYS = {
14692
14696
  pr: "check_prs",
14693
14697
  forge: "forge_provider",
@@ -14730,7 +14734,8 @@ var init_types = __esm(() => {
14730
14734
  user: exports_external.string().trim().min(1, { message: "must not be empty if provided" }).nullable().default(null),
14731
14735
  repos: exports_external.record(exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, "Invalid repo key format"), RepoConfigSchema),
14732
14736
  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 })
14737
+ ports: PortsConfigSchema.default({ min: 4100, max: 4999 }),
14738
+ tui: TuiConfigSchema.default({ leftPaneWidthWeight: 3, rightPaneWidthWeight: 7 })
14734
14739
  });
14735
14740
  VERSION = getVersion();
14736
14741
  });
@@ -25010,80 +25015,6 @@ var init_owner = __esm(() => {
25010
25015
  init_git();
25011
25016
  });
25012
25017
 
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
25018
  // src/lib/forge/types.ts
25088
25019
  function displayStateRank(state) {
25089
25020
  return DISPLAY_PRIORITY.indexOf(state);
@@ -25935,28 +25866,133 @@ var init_WorktreeTable = __esm(() => {
25935
25866
  init_use_tap();
25936
25867
  });
25937
25868
 
25938
- // src/tui/components/DetailPane.tsx
25939
- import { jsx as jsx2, jsxs as jsxs2, Fragment } from "@opentui/react/jsx-runtime";
25940
- function DetailPane({ selectedRow }) {
25869
+ // src/tui/tabs/TabBar.tsx
25870
+ import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
25871
+ function TabItem({
25872
+ id,
25873
+ label,
25874
+ closable,
25875
+ active,
25876
+ onSelect,
25877
+ onClose
25878
+ }) {
25879
+ const tap = useTapHandler(() => onSelect(id));
25880
+ const closeTap = useTapHandler((e) => {
25881
+ e.stopPropagation();
25882
+ onClose?.(id);
25883
+ });
25884
+ return /* @__PURE__ */ jsxs2("box", {
25885
+ flexDirection: "row",
25886
+ paddingRight: 1,
25887
+ ...tap,
25888
+ children: [
25889
+ /* @__PURE__ */ jsx2("text", {
25890
+ fg: active ? tokens.accent : tokens.dim,
25891
+ attributes: active ? 1 : 0,
25892
+ children: label
25893
+ }),
25894
+ closable ? /* @__PURE__ */ jsx2("box", {
25895
+ ...closeTap,
25896
+ style: { marginLeft: 1 },
25897
+ children: /* @__PURE__ */ jsx2("text", {
25898
+ fg: active ? tokens.accent : tokens.dim,
25899
+ children: "✕"
25900
+ })
25901
+ }) : null,
25902
+ /* @__PURE__ */ jsx2("text", {
25903
+ children: " "
25904
+ })
25905
+ ]
25906
+ });
25907
+ }
25908
+ function TabBar({ tabs, activeId, onSelect, onClose }) {
25909
+ return /* @__PURE__ */ jsx2("box", {
25910
+ flexDirection: "row",
25911
+ width: "100%",
25912
+ alignItems: "center",
25913
+ paddingLeft: 1,
25914
+ children: tabs.map((tab) => /* @__PURE__ */ jsx2(TabItem, {
25915
+ id: tab.id,
25916
+ label: tab.label,
25917
+ closable: tab.closable,
25918
+ active: tab.id === activeId,
25919
+ onSelect,
25920
+ onClose
25921
+ }, tab.id))
25922
+ });
25923
+ }
25924
+ var init_TabBar = __esm(() => {
25925
+ init_theme();
25926
+ init_use_tap();
25927
+ });
25928
+
25929
+ // src/tui/tabs/TabPane.tsx
25930
+ import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
25931
+ function TabPane({
25932
+ selectedRow,
25933
+ tabs,
25934
+ activeId,
25935
+ focused,
25936
+ canAdd,
25937
+ onSelect,
25938
+ onAdd,
25939
+ onClose,
25940
+ onFocusTerminal: _onFocusTerminal,
25941
+ onBlurTerminal: _onBlurTerminal
25942
+ }) {
25943
+ const activeTab = tabs.find((t) => t.id === activeId) ?? tabs[0] ?? null;
25944
+ const borderColor = focused ? tokens.accent : tokens.border;
25945
+ return /* @__PURE__ */ jsxs3("box", {
25946
+ flexDirection: "column",
25947
+ flexGrow: 1,
25948
+ width: "100%",
25949
+ height: "100%",
25950
+ border: true,
25951
+ borderColor,
25952
+ focusedBorderColor: borderColor,
25953
+ focusable: false,
25954
+ id: "detail-pane",
25955
+ children: [
25956
+ /* @__PURE__ */ jsx3(TabBar, {
25957
+ tabs,
25958
+ activeId,
25959
+ canAdd,
25960
+ onSelect,
25961
+ onAdd,
25962
+ onClose
25963
+ }),
25964
+ /* @__PURE__ */ jsx3("box", {
25965
+ flexGrow: 1,
25966
+ width: "100%",
25967
+ flexDirection: "column",
25968
+ children: activeTab ? activeTab.render({ worktree: selectedRow, isActive: true }) : /* @__PURE__ */ jsx3("text", {
25969
+ fg: tokens.dim,
25970
+ children: "No tab"
25971
+ })
25972
+ })
25973
+ ]
25974
+ });
25975
+ }
25976
+ var init_TabPane = __esm(() => {
25977
+ init_theme();
25978
+ init_TabBar();
25979
+ });
25980
+
25981
+ // src/tui/components/DetailsTab.tsx
25982
+ import { jsx as jsx4, jsxs as jsxs4, Fragment } from "@opentui/react/jsx-runtime";
25983
+ function DetailsContent({ selectedRow }) {
25941
25984
  const prTap = useTapHandler(() => {
25942
25985
  if (selectedRow?.prUrl)
25943
25986
  openInBrowser(selectedRow.prUrl);
25944
25987
  });
25945
25988
  if (!selectedRow) {
25946
- return /* @__PURE__ */ jsx2("box", {
25947
- id: "detail-pane",
25989
+ return /* @__PURE__ */ jsx4("box", {
25948
25990
  flexGrow: 1,
25949
25991
  width: "100%",
25950
25992
  height: "100%",
25951
- border: true,
25952
- borderColor: tokens.border,
25953
- focusedBorderColor: tokens.border,
25954
- focusable: false,
25955
- title: "Details",
25956
- padding: 1,
25957
25993
  justifyContent: "center",
25958
25994
  alignItems: "center",
25959
- children: /* @__PURE__ */ jsx2("text", {
25995
+ children: /* @__PURE__ */ jsx4("text", {
25960
25996
  fg: tokens.dim,
25961
25997
  children: "No worktree selected"
25962
25998
  })
@@ -25984,26 +26020,20 @@ function DetailPane({ selectedRow }) {
25984
26020
  baseChanged
25985
26021
  } = selectedRow;
25986
26022
  const aheadBehindStr = ahead !== null && behind !== null ? `${ahead} ahead, ${behind} behind` : "unknown";
25987
- return /* @__PURE__ */ jsxs2("scrollbox", {
25988
- id: "detail-pane",
26023
+ return /* @__PURE__ */ jsxs4("scrollbox", {
25989
26024
  flexGrow: 1,
25990
26025
  width: "100%",
25991
26026
  height: "100%",
25992
- border: true,
25993
- borderColor: tokens.border,
25994
- focusedBorderColor: tokens.border,
25995
- focusable: false,
25996
- title: "Details",
25997
- padding: 1,
25998
26027
  focused: false,
26028
+ padding: 1,
25999
26029
  children: [
26000
- /* @__PURE__ */ jsxs2("text", {
26030
+ /* @__PURE__ */ jsxs4("text", {
26001
26031
  children: [
26002
- /* @__PURE__ */ jsx2("span", {
26032
+ /* @__PURE__ */ jsx4("span", {
26003
26033
  fg: tokens.dim,
26004
26034
  children: "Repo:"
26005
26035
  }),
26006
- /* @__PURE__ */ jsxs2("span", {
26036
+ /* @__PURE__ */ jsxs4("span", {
26007
26037
  fg: tokens.fg,
26008
26038
  children: [
26009
26039
  " ",
@@ -26012,13 +26042,13 @@ function DetailPane({ selectedRow }) {
26012
26042
  })
26013
26043
  ]
26014
26044
  }),
26015
- /* @__PURE__ */ jsxs2("text", {
26045
+ /* @__PURE__ */ jsxs4("text", {
26016
26046
  children: [
26017
- /* @__PURE__ */ jsx2("span", {
26047
+ /* @__PURE__ */ jsx4("span", {
26018
26048
  fg: tokens.dim,
26019
26049
  children: "Branch:"
26020
26050
  }),
26021
- /* @__PURE__ */ jsxs2("span", {
26051
+ /* @__PURE__ */ jsxs4("span", {
26022
26052
  fg: tokens.fg,
26023
26053
  children: [
26024
26054
  " ",
@@ -26029,13 +26059,13 @@ function DetailPane({ selectedRow }) {
26029
26059
  })
26030
26060
  ]
26031
26061
  }),
26032
- /* @__PURE__ */ jsxs2("text", {
26062
+ /* @__PURE__ */ jsxs4("text", {
26033
26063
  children: [
26034
- /* @__PURE__ */ jsx2("span", {
26064
+ /* @__PURE__ */ jsx4("span", {
26035
26065
  fg: tokens.dim,
26036
26066
  children: "Path:"
26037
26067
  }),
26038
- /* @__PURE__ */ jsxs2("span", {
26068
+ /* @__PURE__ */ jsxs4("span", {
26039
26069
  fg: tokens.fg,
26040
26070
  children: [
26041
26071
  " ",
@@ -26044,13 +26074,13 @@ function DetailPane({ selectedRow }) {
26044
26074
  })
26045
26075
  ]
26046
26076
  }),
26047
- /* @__PURE__ */ jsxs2("text", {
26077
+ /* @__PURE__ */ jsxs4("text", {
26048
26078
  children: [
26049
- /* @__PURE__ */ jsx2("span", {
26079
+ /* @__PURE__ */ jsx4("span", {
26050
26080
  fg: tokens.dim,
26051
26081
  children: "Commit:"
26052
26082
  }),
26053
- /* @__PURE__ */ jsxs2("span", {
26083
+ /* @__PURE__ */ jsxs4("span", {
26054
26084
  fg: tokens.fg,
26055
26085
  children: [
26056
26086
  " ",
@@ -26059,13 +26089,13 @@ function DetailPane({ selectedRow }) {
26059
26089
  })
26060
26090
  ]
26061
26091
  }),
26062
- owner && /* @__PURE__ */ jsxs2("text", {
26092
+ owner && /* @__PURE__ */ jsxs4("text", {
26063
26093
  children: [
26064
- /* @__PURE__ */ jsx2("span", {
26094
+ /* @__PURE__ */ jsx4("span", {
26065
26095
  fg: tokens.dim,
26066
26096
  children: "Owner:"
26067
26097
  }),
26068
- /* @__PURE__ */ jsxs2("span", {
26098
+ /* @__PURE__ */ jsxs4("span", {
26069
26099
  fg: tokens.fg,
26070
26100
  children: [
26071
26101
  " ",
@@ -26074,13 +26104,13 @@ function DetailPane({ selectedRow }) {
26074
26104
  })
26075
26105
  ]
26076
26106
  }),
26077
- /* @__PURE__ */ jsxs2("text", {
26107
+ /* @__PURE__ */ jsxs4("text", {
26078
26108
  children: [
26079
- /* @__PURE__ */ jsx2("span", {
26109
+ /* @__PURE__ */ jsx4("span", {
26080
26110
  fg: tokens.dim,
26081
26111
  children: "Status:"
26082
26112
  }),
26083
- /* @__PURE__ */ jsxs2("span", {
26113
+ /* @__PURE__ */ jsxs4("span", {
26084
26114
  fg: tokens.fg,
26085
26115
  children: [
26086
26116
  " ",
@@ -26089,13 +26119,13 @@ function DetailPane({ selectedRow }) {
26089
26119
  })
26090
26120
  ]
26091
26121
  }),
26092
- base2 && /* @__PURE__ */ jsxs2("text", {
26122
+ base2 && /* @__PURE__ */ jsxs4("text", {
26093
26123
  children: [
26094
- /* @__PURE__ */ jsx2("span", {
26124
+ /* @__PURE__ */ jsx4("span", {
26095
26125
  fg: tokens.dim,
26096
26126
  children: "Base:"
26097
26127
  }),
26098
- /* @__PURE__ */ jsxs2("span", {
26128
+ /* @__PURE__ */ jsxs4("span", {
26099
26129
  fg: tokens.accent,
26100
26130
  children: [
26101
26131
  " ",
@@ -26104,26 +26134,26 @@ function DetailPane({ selectedRow }) {
26104
26134
  })
26105
26135
  ]
26106
26136
  }),
26107
- baseChanged && /* @__PURE__ */ jsxs2("text", {
26137
+ baseChanged && /* @__PURE__ */ jsxs4("text", {
26108
26138
  children: [
26109
- /* @__PURE__ */ jsx2("span", {
26139
+ /* @__PURE__ */ jsx4("span", {
26110
26140
  fg: tokens.dim,
26111
26141
  children: "Base state:"
26112
26142
  }),
26113
- /* @__PURE__ */ jsx2("span", {
26143
+ /* @__PURE__ */ jsx4("span", {
26114
26144
  fg: tokens.warning,
26115
26145
  children: " moved since recorded"
26116
26146
  })
26117
26147
  ]
26118
26148
  }),
26119
- /* @__PURE__ */ jsxs2("text", {
26149
+ /* @__PURE__ */ jsxs4("text", {
26120
26150
  style: { marginTop: 1 },
26121
26151
  children: [
26122
- /* @__PURE__ */ jsx2("span", {
26152
+ /* @__PURE__ */ jsx4("span", {
26123
26153
  fg: tokens.dim,
26124
26154
  children: base2 ? "vs base:" : "vs main:"
26125
26155
  }),
26126
- /* @__PURE__ */ jsxs2("span", {
26156
+ /* @__PURE__ */ jsxs4("span", {
26127
26157
  fg: tokens.fg,
26128
26158
  children: [
26129
26159
  " ",
@@ -26132,13 +26162,13 @@ function DetailPane({ selectedRow }) {
26132
26162
  })
26133
26163
  ]
26134
26164
  }),
26135
- /* @__PURE__ */ jsxs2("text", {
26165
+ /* @__PURE__ */ jsxs4("text", {
26136
26166
  children: [
26137
- /* @__PURE__ */ jsx2("span", {
26167
+ /* @__PURE__ */ jsx4("span", {
26138
26168
  fg: tokens.dim,
26139
26169
  children: "Deps:"
26140
26170
  }),
26141
- /* @__PURE__ */ jsxs2("span", {
26171
+ /* @__PURE__ */ jsxs4("span", {
26142
26172
  fg: tokens.fg,
26143
26173
  children: [
26144
26174
  " ",
@@ -26147,13 +26177,13 @@ function DetailPane({ selectedRow }) {
26147
26177
  })
26148
26178
  ]
26149
26179
  }),
26150
- rebaseStatus && /* @__PURE__ */ jsxs2("text", {
26180
+ rebaseStatus && /* @__PURE__ */ jsxs4("text", {
26151
26181
  children: [
26152
- /* @__PURE__ */ jsx2("span", {
26182
+ /* @__PURE__ */ jsx4("span", {
26153
26183
  fg: tokens.dim,
26154
26184
  children: "Rebase:"
26155
26185
  }),
26156
- /* @__PURE__ */ jsxs2("span", {
26186
+ /* @__PURE__ */ jsxs4("span", {
26157
26187
  fg: tokens.error,
26158
26188
  children: [
26159
26189
  " ",
@@ -26162,12 +26192,12 @@ function DetailPane({ selectedRow }) {
26162
26192
  })
26163
26193
  ]
26164
26194
  }),
26165
- prNumber !== null && /* @__PURE__ */ jsxs2(Fragment, {
26195
+ prNumber !== null && /* @__PURE__ */ jsxs4(Fragment, {
26166
26196
  children: [
26167
- /* @__PURE__ */ jsxs2("text", {
26197
+ /* @__PURE__ */ jsxs4("text", {
26168
26198
  style: { marginTop: 1 },
26169
26199
  children: [
26170
- /* @__PURE__ */ jsxs2("span", {
26200
+ /* @__PURE__ */ jsxs4("span", {
26171
26201
  fg: tokens.accent,
26172
26202
  children: [
26173
26203
  "PR #",
@@ -26175,7 +26205,7 @@ function DetailPane({ selectedRow }) {
26175
26205
  ":"
26176
26206
  ]
26177
26207
  }),
26178
- /* @__PURE__ */ jsxs2("span", {
26208
+ /* @__PURE__ */ jsxs4("span", {
26179
26209
  fg: tokens.dim,
26180
26210
  children: [
26181
26211
  " ",
@@ -26184,13 +26214,13 @@ function DetailPane({ selectedRow }) {
26184
26214
  })
26185
26215
  ]
26186
26216
  }),
26187
- prChecks && /* @__PURE__ */ jsxs2("text", {
26217
+ prChecks && /* @__PURE__ */ jsxs4("text", {
26188
26218
  children: [
26189
- /* @__PURE__ */ jsx2("span", {
26219
+ /* @__PURE__ */ jsx4("span", {
26190
26220
  fg: tokens.dim,
26191
26221
  children: "Checks:"
26192
26222
  }),
26193
- /* @__PURE__ */ jsxs2("span", {
26223
+ /* @__PURE__ */ jsxs4("span", {
26194
26224
  fg: tokens.fg,
26195
26225
  children: [
26196
26226
  " ",
@@ -26199,20 +26229,20 @@ function DetailPane({ selectedRow }) {
26199
26229
  })
26200
26230
  ]
26201
26231
  }),
26202
- prUrl && /* @__PURE__ */ jsx2("box", {
26232
+ prUrl && /* @__PURE__ */ jsx4("box", {
26203
26233
  ...prTap,
26204
- children: /* @__PURE__ */ jsxs2("text", {
26234
+ children: /* @__PURE__ */ jsxs4("text", {
26205
26235
  selectable: false,
26206
26236
  children: [
26207
- /* @__PURE__ */ jsx2("span", {
26237
+ /* @__PURE__ */ jsx4("span", {
26208
26238
  fg: tokens.dim,
26209
26239
  children: "URL:"
26210
26240
  }),
26211
- /* @__PURE__ */ jsx2("span", {
26241
+ /* @__PURE__ */ jsx4("span", {
26212
26242
  fg: tokens.accent,
26213
26243
  children: ` ${prUrl}`
26214
26244
  }),
26215
- /* @__PURE__ */ jsx2("span", {
26245
+ /* @__PURE__ */ jsx4("span", {
26216
26246
  fg: tokens.dim,
26217
26247
  children: " ↗ click"
26218
26248
  })
@@ -26221,11 +26251,11 @@ function DetailPane({ selectedRow }) {
26221
26251
  })
26222
26252
  ]
26223
26253
  }),
26224
- dirtyFiles.length > 0 && /* @__PURE__ */ jsxs2("box", {
26254
+ dirtyFiles.length > 0 && /* @__PURE__ */ jsxs4("box", {
26225
26255
  flexDirection: "column",
26226
26256
  style: { marginTop: 1 },
26227
26257
  children: [
26228
- /* @__PURE__ */ jsxs2("text", {
26258
+ /* @__PURE__ */ jsxs4("text", {
26229
26259
  fg: tokens.warning,
26230
26260
  children: [
26231
26261
  "Dirty Files (",
@@ -26233,14 +26263,14 @@ function DetailPane({ selectedRow }) {
26233
26263
  "):"
26234
26264
  ]
26235
26265
  }),
26236
- dirtyFiles.slice(0, 20).map((file2, idx) => /* @__PURE__ */ jsxs2("text", {
26266
+ dirtyFiles.slice(0, 20).map((file2, idx) => /* @__PURE__ */ jsxs4("text", {
26237
26267
  fg: tokens.warning,
26238
26268
  children: [
26239
26269
  " ",
26240
26270
  file2
26241
26271
  ]
26242
26272
  }, idx)),
26243
- dirtyFiles.length > 20 && /* @__PURE__ */ jsxs2("text", {
26273
+ dirtyFiles.length > 20 && /* @__PURE__ */ jsxs4("text", {
26244
26274
  fg: tokens.warning,
26245
26275
  children: [
26246
26276
  " ...and ",
@@ -26250,17 +26280,17 @@ function DetailPane({ selectedRow }) {
26250
26280
  })
26251
26281
  ]
26252
26282
  }),
26253
- /* @__PURE__ */ jsxs2("box", {
26283
+ /* @__PURE__ */ jsxs4("box", {
26254
26284
  flexDirection: "column",
26255
26285
  style: { marginTop: 1 },
26256
26286
  children: [
26257
- /* @__PURE__ */ jsx2("text", {
26287
+ /* @__PURE__ */ jsx4("text", {
26258
26288
  fg: tokens.dim,
26259
26289
  children: "Actions:"
26260
26290
  }),
26261
- !isMainCheckout && /* @__PURE__ */ jsxs2(Fragment, {
26291
+ !isMainCheckout && /* @__PURE__ */ jsxs4(Fragment, {
26262
26292
  children: [
26263
- /* @__PURE__ */ jsxs2("text", {
26293
+ /* @__PURE__ */ jsxs4("text", {
26264
26294
  fg: tokens.dim,
26265
26295
  children: [
26266
26296
  " i install deps (",
@@ -26268,44 +26298,218 @@ function DetailPane({ selectedRow }) {
26268
26298
  ")"
26269
26299
  ]
26270
26300
  }),
26271
- /* @__PURE__ */ jsx2("text", {
26301
+ /* @__PURE__ */ jsx4("text", {
26272
26302
  fg: tokens.dim,
26273
26303
  children: " m rename worktree"
26274
26304
  })
26275
26305
  ]
26276
26306
  }),
26277
- /* @__PURE__ */ jsx2("text", {
26307
+ /* @__PURE__ */ jsx4("text", {
26278
26308
  fg: tokens.dim,
26279
26309
  children: " p pull branch"
26280
26310
  }),
26281
- /* @__PURE__ */ jsx2("text", {
26311
+ /* @__PURE__ */ jsx4("text", {
26282
26312
  fg: tokens.dim,
26283
26313
  children: " b rebase · s sync · o open in IDE · d remove"
26314
+ }),
26315
+ /* @__PURE__ */ jsx4("text", {
26316
+ fg: tokens.dim,
26317
+ children: " t new terminal session (max 5)"
26284
26318
  })
26285
26319
  ]
26286
26320
  })
26287
26321
  ]
26288
26322
  });
26289
26323
  }
26290
- var init_DetailPane = __esm(() => {
26324
+ var init_DetailsTab = __esm(() => {
26325
+ init_theme();
26326
+ init_use_tap();
26327
+ });
26328
+
26329
+ // src/tui/components/TerminalView.tsx
26330
+ import { useState as useState2, useEffect as useEffect3, useRef as useRef4, useCallback as useCallback3 } from "react";
26331
+ import { jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
26332
+ function TerminalView({ session, focused, onFocus, onSend, onResize, registerListener, unregisterListener }) {
26333
+ const [draft, setDraft] = useState2("");
26334
+ const focusTap = useTapHandler(() => onFocus());
26335
+ const termRef = useRef4(null);
26336
+ const handleData = useCallback3((data, source) => {
26337
+ if (source !== "input")
26338
+ return;
26339
+ if (session.usePty && focused)
26340
+ return;
26341
+ onSend(data);
26342
+ }, [onSend, focused, session.id, session.usePty]);
26343
+ const handleResize = useCallback3((cols, rows) => {
26344
+ onResize?.(cols, rows);
26345
+ }, [onResize]);
26346
+ useEffect3(() => {
26347
+ if (!session.usePty || !session.terminal)
26348
+ return;
26349
+ if (!termRef.current || !registerListener || !unregisterListener)
26350
+ return;
26351
+ try {
26352
+ termRef.current.write(new TextEncoder().encode("\x1B[2J\x1B[H"));
26353
+ } catch {}
26354
+ const write = (data) => {
26355
+ try {
26356
+ termRef.current?.write(data);
26357
+ } catch {}
26358
+ };
26359
+ registerListener(session.id, write);
26360
+ return () => unregisterListener(session.id);
26361
+ }, [session.id, session.usePty, session.terminal, registerListener, unregisterListener]);
26362
+ useEffect3(() => {
26363
+ if (!session.usePty)
26364
+ return;
26365
+ if (!termRef.current)
26366
+ return;
26367
+ try {
26368
+ if (focused)
26369
+ termRef.current.focus?.();
26370
+ else
26371
+ termRef.current.blur?.();
26372
+ } catch {}
26373
+ }, [focused, session.usePty]);
26374
+ if (session.usePty && session.terminal) {
26375
+ return /* @__PURE__ */ jsxs5("box", {
26376
+ flexDirection: "column",
26377
+ flexGrow: 1,
26378
+ width: "100%",
26379
+ height: "100%",
26380
+ ...focusTap,
26381
+ children: [
26382
+ /* @__PURE__ */ jsx5("box", {
26383
+ flexGrow: 1,
26384
+ width: "100%",
26385
+ height: "100%",
26386
+ flexDirection: "column",
26387
+ padding: 1,
26388
+ children: /* @__PURE__ */ jsx5("embedded-terminal", {
26389
+ ref: termRef,
26390
+ width: "100%",
26391
+ height: "100%",
26392
+ cols: session.cols,
26393
+ rows: session.rows,
26394
+ maxScrollback: 1000,
26395
+ onData: handleData,
26396
+ onTerminalResize: handleResize,
26397
+ selectable: true,
26398
+ focused
26399
+ })
26400
+ }),
26401
+ session.exited !== null && /* @__PURE__ */ jsxs5("text", {
26402
+ fg: tokens.warning,
26403
+ children: [
26404
+ "— exited with code ",
26405
+ session.exited,
26406
+ " — press t for new session"
26407
+ ]
26408
+ }),
26409
+ !focused && /* @__PURE__ */ jsxs5("text", {
26410
+ fg: tokens.dim,
26411
+ children: [
26412
+ "Click to focus · Ctrl+G or click table to unfocus · ",
26413
+ session.label,
26414
+ " PTY"
26415
+ ]
26416
+ }),
26417
+ focused && /* @__PURE__ */ jsx5("text", {
26418
+ fg: tokens.accent,
26419
+ children: "Focused PTY — all keys go to shell · Ctrl+G to unfocus"
26420
+ })
26421
+ ]
26422
+ });
26423
+ }
26424
+ const lines = session.lines;
26425
+ return /* @__PURE__ */ jsxs5("box", {
26426
+ flexDirection: "column",
26427
+ flexGrow: 1,
26428
+ width: "100%",
26429
+ height: "100%",
26430
+ ...focusTap,
26431
+ children: [
26432
+ /* @__PURE__ */ jsxs5("scrollbox", {
26433
+ flexGrow: 1,
26434
+ width: "100%",
26435
+ border: false,
26436
+ focused: false,
26437
+ style: { marginBottom: 1 },
26438
+ children: [
26439
+ lines.length === 0 ? /* @__PURE__ */ jsx5("text", {
26440
+ fg: tokens.dim,
26441
+ children: "No output yet — type a command below."
26442
+ }) : lines.slice(-200).map((line, idx) => /* @__PURE__ */ jsx5("text", {
26443
+ fg: session.exited !== null ? tokens.dim : tokens.fg,
26444
+ children: line || " "
26445
+ }, idx)),
26446
+ session.exited !== null && /* @__PURE__ */ jsxs5("text", {
26447
+ fg: tokens.warning,
26448
+ children: [
26449
+ "— exited with code ",
26450
+ session.exited,
26451
+ " —"
26452
+ ]
26453
+ })
26454
+ ]
26455
+ }),
26456
+ /* @__PURE__ */ jsxs5("box", {
26457
+ flexDirection: "row",
26458
+ gap: 1,
26459
+ width: "100%",
26460
+ children: [
26461
+ /* @__PURE__ */ jsx5("text", {
26462
+ fg: tokens.dim,
26463
+ children: "$"
26464
+ }),
26465
+ /* @__PURE__ */ jsx5("box", {
26466
+ flexGrow: 1,
26467
+ children: /* @__PURE__ */ jsx5("input", {
26468
+ focused,
26469
+ value: draft,
26470
+ placeholder: focused ? "Type command, Enter to send, Esc to unfocus" : "Click to focus terminal",
26471
+ onInput: (v) => setDraft(v),
26472
+ onSubmit: () => {
26473
+ if (!draft)
26474
+ return;
26475
+ onSend(draft + `
26476
+ `);
26477
+ setDraft("");
26478
+ }
26479
+ })
26480
+ })
26481
+ ]
26482
+ }),
26483
+ !focused && /* @__PURE__ */ jsx5("text", {
26484
+ fg: tokens.dim,
26485
+ children: "Click terminal or press 't' to focus · Ctrl+G or click table to unfocus"
26486
+ }),
26487
+ focused && /* @__PURE__ */ jsx5("text", {
26488
+ fg: tokens.accent,
26489
+ children: "Focused — typing goes to shell · Ctrl+G to unfocus · max 5 per worktree persist across switches"
26490
+ })
26491
+ ]
26492
+ });
26493
+ }
26494
+ var init_TerminalView = __esm(() => {
26291
26495
  init_theme();
26292
26496
  init_use_tap();
26293
26497
  });
26294
26498
 
26295
26499
  // src/tui/components/Footer.tsx
26296
- import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
26500
+ import { jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
26297
26501
  function HintItem({ hintKey, label, isFirst, onClick }) {
26298
26502
  const tap = useTapHandler(() => onClick?.(hintKey));
26299
- return /* @__PURE__ */ jsx3("box", {
26503
+ return /* @__PURE__ */ jsx6("box", {
26300
26504
  flexDirection: "row",
26301
26505
  ...onClick ? tap : {},
26302
- children: /* @__PURE__ */ jsxs3("text", {
26506
+ children: /* @__PURE__ */ jsxs6("text", {
26303
26507
  children: [
26304
- /* @__PURE__ */ jsx3("span", {
26508
+ /* @__PURE__ */ jsx6("span", {
26305
26509
  fg: tokens.dim,
26306
26510
  children: isFirst ? "" : " · "
26307
26511
  }),
26308
- /* @__PURE__ */ jsx3("span", {
26512
+ /* @__PURE__ */ jsx6("span", {
26309
26513
  fg: tokens.dim,
26310
26514
  children: `${hintKey} ${label}`
26311
26515
  })
@@ -26315,15 +26519,15 @@ function HintItem({ hintKey, label, isFirst, onClick }) {
26315
26519
  }
26316
26520
  function ErrorBadge({ count: count2, onClick }) {
26317
26521
  const tap = useTapHandler(() => onClick?.());
26318
- return /* @__PURE__ */ jsx3("box", {
26522
+ return /* @__PURE__ */ jsx6("box", {
26319
26523
  ...onClick ? tap : {},
26320
- children: /* @__PURE__ */ jsxs3("text", {
26524
+ children: /* @__PURE__ */ jsxs6("text", {
26321
26525
  children: [
26322
- /* @__PURE__ */ jsx3("span", {
26526
+ /* @__PURE__ */ jsx6("span", {
26323
26527
  fg: tokens.error,
26324
26528
  children: `${count2} error${count2 !== 1 ? "s" : ""}`
26325
26529
  }),
26326
- /* @__PURE__ */ jsx3("span", {
26530
+ /* @__PURE__ */ jsx6("span", {
26327
26531
  fg: tokens.dim,
26328
26532
  children: " · e view"
26329
26533
  })
@@ -26333,7 +26537,7 @@ function ErrorBadge({ count: count2, onClick }) {
26333
26537
  }
26334
26538
  function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinnerFrame, filter, onHintClick, onErrorClick }) {
26335
26539
  const frame = spinnerFrame ?? "◌";
26336
- return /* @__PURE__ */ jsxs3("box", {
26540
+ return /* @__PURE__ */ jsxs6("box", {
26337
26541
  id: "footer",
26338
26542
  width: "100%",
26339
26543
  height: 3,
@@ -26344,29 +26548,29 @@ function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinner
26344
26548
  justifyContent: "space-between",
26345
26549
  alignItems: "center",
26346
26550
  children: [
26347
- busyText ? /* @__PURE__ */ jsx3("text", {
26551
+ busyText ? /* @__PURE__ */ jsx6("text", {
26348
26552
  fg: tokens.accent,
26349
26553
  children: `${frame} ${busyText}`
26350
- }) : /* @__PURE__ */ jsx3("box", {
26554
+ }) : /* @__PURE__ */ jsx6("box", {
26351
26555
  flexDirection: "row",
26352
26556
  gap: 1,
26353
- children: HINTS.map(([key, label], idx) => /* @__PURE__ */ jsx3(HintItem, {
26557
+ children: HINTS.map(([key, label], idx) => /* @__PURE__ */ jsx6(HintItem, {
26354
26558
  hintKey: key,
26355
26559
  label,
26356
26560
  isFirst: idx === 0,
26357
26561
  onClick: onHintClick
26358
26562
  }, key))
26359
26563
  }),
26360
- /* @__PURE__ */ jsxs3("box", {
26564
+ /* @__PURE__ */ jsxs6("box", {
26361
26565
  flexDirection: "row",
26362
26566
  gap: 2,
26363
26567
  alignItems: "center",
26364
26568
  children: [
26365
- message ? /* @__PURE__ */ jsx3("text", {
26569
+ message ? /* @__PURE__ */ jsx6("text", {
26366
26570
  fg: tokens.warning,
26367
26571
  children: message
26368
26572
  }) : null,
26369
- filter ? /* @__PURE__ */ jsxs3("text", {
26573
+ filter ? /* @__PURE__ */ jsxs6("text", {
26370
26574
  fg: "magenta",
26371
26575
  children: [
26372
26576
  "filter: ",
@@ -26378,17 +26582,17 @@ function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinner
26378
26582
  ")"
26379
26583
  ]
26380
26584
  }) : null,
26381
- errorCount > 0 ? /* @__PURE__ */ jsx3(ErrorBadge, {
26585
+ errorCount > 0 ? /* @__PURE__ */ jsx6(ErrorBadge, {
26382
26586
  count: errorCount,
26383
26587
  onClick: onErrorClick
26384
26588
  }) : null,
26385
- /* @__PURE__ */ jsx3(HintItem, {
26589
+ /* @__PURE__ */ jsx6(HintItem, {
26386
26590
  hintKey: "?",
26387
26591
  label: "help",
26388
26592
  isFirst: errorCount === 0,
26389
26593
  onClick: onHintClick
26390
26594
  }),
26391
- /* @__PURE__ */ jsx3("text", {
26595
+ /* @__PURE__ */ jsx6("text", {
26392
26596
  fg: tokens.dim,
26393
26597
  children: loading ? `${frame} Refreshing…` : `Updated ${lastRefreshed}`
26394
26598
  })
@@ -26408,16 +26612,16 @@ var init_Footer = __esm(() => {
26408
26612
  });
26409
26613
 
26410
26614
  // src/tui/components/Divider.tsx
26411
- import { useState as useState2, useRef as useRef4, useCallback as useCallback3 } from "react";
26615
+ import { useState as useState3, useRef as useRef5, useCallback as useCallback4 } from "react";
26412
26616
  import { useRenderer } from "@opentui/react";
26413
- import { jsx as jsx4 } from "@opentui/react/jsx-runtime";
26617
+ import { jsx as jsx7 } from "@opentui/react/jsx-runtime";
26414
26618
  function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26415
26619
  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) => {
26620
+ const [hovered, setHovered] = useState3(false);
26621
+ const [dragging, setDragging] = useState3(false);
26622
+ const startXRef = useRef5(null);
26623
+ const startRatioRef = useRef5(splitRatio);
26624
+ const handleMouseDown = useCallback4((e) => {
26421
26625
  if (e.button !== 0)
26422
26626
  return;
26423
26627
  startXRef.current = e.x;
@@ -26427,7 +26631,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26427
26631
  renderer.clearSelection();
26428
26632
  e.stopPropagation();
26429
26633
  }, [splitRatio, onDraggingChange, renderer]);
26430
- const handleMouseUp = useCallback3((e) => {
26634
+ const handleMouseUp = useCallback4((e) => {
26431
26635
  if (e.button !== 0)
26432
26636
  return;
26433
26637
  startXRef.current = null;
@@ -26435,7 +26639,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26435
26639
  onDraggingChange?.(false);
26436
26640
  e.stopPropagation();
26437
26641
  }, [onDraggingChange]);
26438
- const handleMouseDrag = useCallback3((e) => {
26642
+ const handleMouseDrag = useCallback4((e) => {
26439
26643
  if (startXRef.current === null)
26440
26644
  return;
26441
26645
  renderer.clearSelection();
@@ -26450,7 +26654,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26450
26654
  onMouseDrag: handleMouseDrag,
26451
26655
  onMouseMove: handleMouseDrag
26452
26656
  };
26453
- return /* @__PURE__ */ jsx4("box", {
26657
+ return /* @__PURE__ */ jsx7("box", {
26454
26658
  width: 3,
26455
26659
  height: "100%",
26456
26660
  backgroundColor: dragging ? tokens.selectionBg : hovered ? tokens.panelBg : undefined,
@@ -26459,7 +26663,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26459
26663
  onMouseOut: () => setHovered(false),
26460
26664
  alignItems: "center",
26461
26665
  justifyContent: "center",
26462
- children: /* @__PURE__ */ jsx4("text", {
26666
+ children: /* @__PURE__ */ jsx7("text", {
26463
26667
  fg: dragging ? tokens.accent : hovered ? tokens.borderActive : tokens.border,
26464
26668
  ...dragHandlers,
26465
26669
  onMouseOver: () => setHovered(true),
@@ -26474,9 +26678,9 @@ var init_Divider = __esm(() => {
26474
26678
  });
26475
26679
 
26476
26680
  // src/tui/components/Overlay.tsx
26477
- import { jsx as jsx5 } from "@opentui/react/jsx-runtime";
26681
+ import { jsx as jsx8 } from "@opentui/react/jsx-runtime";
26478
26682
  function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
26479
- return /* @__PURE__ */ jsx5("box", {
26683
+ return /* @__PURE__ */ jsx8("box", {
26480
26684
  id: "overlay-scrim",
26481
26685
  position: "absolute",
26482
26686
  width: "100%",
@@ -26488,7 +26692,7 @@ function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
26488
26692
  flexDirection: "row",
26489
26693
  justifyContent: "center",
26490
26694
  alignItems: "center",
26491
- children: /* @__PURE__ */ jsx5("box", {
26695
+ children: /* @__PURE__ */ jsx8("box", {
26492
26696
  id: "overlay-panel",
26493
26697
  width,
26494
26698
  border: true,
@@ -26508,37 +26712,38 @@ var init_Overlay = __esm(() => {
26508
26712
  });
26509
26713
 
26510
26714
  // src/tui/components/HelpOverlay.tsx
26511
- import { jsx as jsx6, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
26715
+ import { jsx as jsx9, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
26512
26716
  function HelpOverlay() {
26513
- return /* @__PURE__ */ jsxs4(Overlay, {
26717
+ return /* @__PURE__ */ jsxs7(Overlay, {
26514
26718
  title: "Help",
26515
26719
  borderColor: tokens.border,
26516
26720
  width: 84,
26517
26721
  children: [
26518
- /* @__PURE__ */ jsx6("box", {
26722
+ /* @__PURE__ */ jsx9("box", {
26519
26723
  flexDirection: "column",
26520
26724
  style: { maxHeight: 36 },
26521
- children: /* @__PURE__ */ jsx6("scrollbox", {
26725
+ children: /* @__PURE__ */ jsx9("scrollbox", {
26522
26726
  flexGrow: 1,
26523
- children: HELP_ENTRIES.map(([key, desc]) => /* @__PURE__ */ jsxs4("box", {
26727
+ children: HELP_ENTRIES.map(([key, desc]) => /* @__PURE__ */ jsxs7("box", {
26524
26728
  flexDirection: "row",
26525
26729
  gap: 1,
26526
26730
  children: [
26527
- /* @__PURE__ */ jsx6("box", {
26731
+ /* @__PURE__ */ jsx9("box", {
26528
26732
  width: 18,
26529
26733
  flexShrink: 0,
26530
- children: /* @__PURE__ */ jsx6("text", {
26734
+ justifyContent: "flex-end",
26735
+ children: /* @__PURE__ */ jsx9("text", {
26531
26736
  fg: tokens.accent,
26532
26737
  children: key
26533
26738
  })
26534
26739
  }),
26535
- /* @__PURE__ */ jsx6("text", {
26740
+ /* @__PURE__ */ jsx9("text", {
26536
26741
  fg: tokens.dim,
26537
26742
  children: "-"
26538
26743
  }),
26539
- /* @__PURE__ */ jsx6("box", {
26744
+ /* @__PURE__ */ jsx9("box", {
26540
26745
  flexGrow: 1,
26541
- children: /* @__PURE__ */ jsx6("text", {
26746
+ children: /* @__PURE__ */ jsx9("text", {
26542
26747
  fg: tokens.fg,
26543
26748
  children: desc
26544
26749
  })
@@ -26547,7 +26752,7 @@ function HelpOverlay() {
26547
26752
  }, key))
26548
26753
  })
26549
26754
  }),
26550
- /* @__PURE__ */ jsx6("text", {
26755
+ /* @__PURE__ */ jsx9("text", {
26551
26756
  style: { marginTop: 1, fg: tokens.dim },
26552
26757
  children: "Press any key to close"
26553
26758
  })
@@ -26572,7 +26777,9 @@ var init_HelpOverlay = __esm(() => {
26572
26777
  ["i", "Install dependencies in selection (worktrees and main)"],
26573
26778
  ["m", "Rename selected worktree (branch + directory)"],
26574
26779
  ["o", "Open selected in IDE"],
26575
- ["a", "Spawn agent in selected"],
26780
+ ["t", "New terminal session (max 5 per worktree, Details ↔ Session tabs)"],
26781
+ ["click tab", "Switch Details ↔ Session (professional tabs with ▎ active)"],
26782
+ ["Ctrl+G / click table", "Leave terminal focus — focused terminal gets all keys"],
26576
26783
  ["d", "Remove selected worktree(s)"],
26577
26784
  ["e", "View data warnings (when count > 0)"],
26578
26785
  ["r", "Refresh data"],
@@ -26586,50 +26793,50 @@ var init_HelpOverlay = __esm(() => {
26586
26793
  });
26587
26794
 
26588
26795
  // src/tui/components/ConfirmModal.tsx
26589
- import { jsx as jsx7, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
26796
+ import { jsx as jsx10, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
26590
26797
  function ConfirmModal({ title, message, onConfirm, onCancel }) {
26591
26798
  const yesTap = useTapHandler(() => onConfirm?.());
26592
26799
  const noTap = useTapHandler(() => onCancel?.());
26593
- return /* @__PURE__ */ jsx7(Overlay, {
26800
+ return /* @__PURE__ */ jsx10(Overlay, {
26594
26801
  title,
26595
26802
  borderColor: tokens.warning,
26596
- children: /* @__PURE__ */ jsxs5("box", {
26803
+ children: /* @__PURE__ */ jsxs8("box", {
26597
26804
  flexDirection: "column",
26598
26805
  justifyContent: "center",
26599
26806
  alignItems: "center",
26600
26807
  children: [
26601
- /* @__PURE__ */ jsx7("text", {
26808
+ /* @__PURE__ */ jsx10("text", {
26602
26809
  children: message
26603
26810
  }),
26604
- /* @__PURE__ */ jsxs5("box", {
26811
+ /* @__PURE__ */ jsxs8("box", {
26605
26812
  flexDirection: "row",
26606
26813
  gap: 2,
26607
26814
  style: { marginTop: 2 },
26608
26815
  children: [
26609
- /* @__PURE__ */ jsx7("box", {
26816
+ /* @__PURE__ */ jsx10("box", {
26610
26817
  ...onConfirm ? yesTap : {},
26611
- children: /* @__PURE__ */ jsxs5("text", {
26818
+ children: /* @__PURE__ */ jsxs8("text", {
26612
26819
  children: [
26613
- /* @__PURE__ */ jsx7("span", {
26820
+ /* @__PURE__ */ jsx10("span", {
26614
26821
  fg: tokens.dim,
26615
26822
  children: "[y] "
26616
26823
  }),
26617
- /* @__PURE__ */ jsx7("span", {
26824
+ /* @__PURE__ */ jsx10("span", {
26618
26825
  fg: tokens.success,
26619
26826
  children: "Yes"
26620
26827
  })
26621
26828
  ]
26622
26829
  })
26623
26830
  }),
26624
- /* @__PURE__ */ jsx7("box", {
26831
+ /* @__PURE__ */ jsx10("box", {
26625
26832
  ...onCancel ? noTap : {},
26626
- children: /* @__PURE__ */ jsxs5("text", {
26833
+ children: /* @__PURE__ */ jsxs8("text", {
26627
26834
  children: [
26628
- /* @__PURE__ */ jsx7("span", {
26835
+ /* @__PURE__ */ jsx10("span", {
26629
26836
  fg: tokens.dim,
26630
26837
  children: "[n/esc] "
26631
26838
  }),
26632
- /* @__PURE__ */ jsx7("span", {
26839
+ /* @__PURE__ */ jsx10("span", {
26633
26840
  children: "Cancel"
26634
26841
  })
26635
26842
  ]
@@ -26705,52 +26912,52 @@ async function runWtxAction(args, onLine) {
26705
26912
  }
26706
26913
 
26707
26914
  // src/tui/components/ActionLogModal.tsx
26708
- import { jsx as jsx8, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
26915
+ import { jsx as jsx11, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
26709
26916
  function ActionLogModal({ title, lines, done, exitCode, remaining }) {
26710
- return /* @__PURE__ */ jsxs6(Overlay, {
26917
+ return /* @__PURE__ */ jsxs9(Overlay, {
26711
26918
  title,
26712
26919
  borderColor: tokens.border,
26713
26920
  children: [
26714
- /* @__PURE__ */ jsx8("box", {
26921
+ /* @__PURE__ */ jsx11("box", {
26715
26922
  flexGrow: 1,
26716
26923
  flexDirection: "column",
26717
26924
  style: { minHeight: 10, maxHeight: 20 },
26718
- children: /* @__PURE__ */ jsx8("scrollbox", {
26925
+ children: /* @__PURE__ */ jsx11("scrollbox", {
26719
26926
  flexGrow: 1,
26720
26927
  stickyScroll: true,
26721
- children: lines.map((line, i2) => /* @__PURE__ */ jsx8("text", {
26928
+ children: lines.map((line, i2) => /* @__PURE__ */ jsx11("text", {
26722
26929
  fg: line.type === "err" ? tokens.error : tokens.fg,
26723
26930
  children: line.text
26724
26931
  }, i2))
26725
26932
  })
26726
26933
  }),
26727
- /* @__PURE__ */ jsxs6("box", {
26934
+ /* @__PURE__ */ jsxs9("box", {
26728
26935
  marginTop: 1,
26729
26936
  flexDirection: "row",
26730
26937
  justifyContent: "center",
26731
26938
  children: [
26732
- !done && /* @__PURE__ */ jsx8("text", {
26939
+ !done && /* @__PURE__ */ jsx11("text", {
26733
26940
  fg: tokens.warning,
26734
26941
  children: "Running..."
26735
26942
  }),
26736
- done && exitCode === 0 && /* @__PURE__ */ jsx8("text", {
26943
+ done && exitCode === 0 && /* @__PURE__ */ jsx11("text", {
26737
26944
  fg: tokens.success,
26738
26945
  children: "✓ Done (exit 0)"
26739
26946
  }),
26740
- done && exitCode !== 0 && /* @__PURE__ */ jsxs6("text", {
26947
+ done && exitCode !== 0 && /* @__PURE__ */ jsxs9("text", {
26741
26948
  children: [
26742
- /* @__PURE__ */ jsxs6("span", {
26949
+ /* @__PURE__ */ jsxs9("span", {
26743
26950
  fg: tokens.error,
26744
26951
  children: [
26745
26952
  "✗ Exit ",
26746
26953
  exitCode
26747
26954
  ]
26748
26955
  }),
26749
- remaining !== undefined && remaining > 0 && /* @__PURE__ */ jsx8("span", {
26956
+ remaining !== undefined && remaining > 0 && /* @__PURE__ */ jsx11("span", {
26750
26957
  fg: tokens.warning,
26751
26958
  children: ` · ${remaining} more failure${remaining > 1 ? "s" : ""}`
26752
26959
  }),
26753
- /* @__PURE__ */ jsx8("span", {
26960
+ /* @__PURE__ */ jsx11("span", {
26754
26961
  fg: tokens.dim,
26755
26962
  children: " - press any key to close"
26756
26963
  })
@@ -26837,8 +27044,8 @@ var init_history = __esm(() => {
26837
27044
  });
26838
27045
 
26839
27046
  // 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";
27047
+ import { useEffect as useEffect4, useState as useState4 } from "react";
27048
+ import { jsx as jsx12, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
26842
27049
  function formatShortTime(iso) {
26843
27050
  const d = new Date(iso);
26844
27051
  if (Number.isNaN(d.getTime()))
@@ -26851,16 +27058,16 @@ function HistoryEntryLine({ entry }) {
26851
27058
  const duration3 = entry.durationMs !== null && entry.durationMs !== undefined ? ` ${entry.durationMs}ms` : "";
26852
27059
  const prefix = `${formatShortTime(entry.ts)} ${entry.source.padEnd(8)} ${args}`;
26853
27060
  if (entry.exit === null || entry.exit === undefined) {
26854
- return /* @__PURE__ */ jsxs7("text", {
27061
+ return /* @__PURE__ */ jsxs10("text", {
26855
27062
  children: [
26856
- /* @__PURE__ */ jsxs7("span", {
27063
+ /* @__PURE__ */ jsxs10("span", {
26857
27064
  fg: tokens.dim,
26858
27065
  children: [
26859
27066
  "◌ ",
26860
27067
  prefix
26861
27068
  ]
26862
27069
  }),
26863
- duration3 && /* @__PURE__ */ jsx9("span", {
27070
+ duration3 && /* @__PURE__ */ jsx12("span", {
26864
27071
  fg: tokens.dim,
26865
27072
  children: duration3
26866
27073
  })
@@ -26868,32 +27075,32 @@ function HistoryEntryLine({ entry }) {
26868
27075
  });
26869
27076
  }
26870
27077
  if (entry.exit === 0) {
26871
- return /* @__PURE__ */ jsxs7("text", {
27078
+ return /* @__PURE__ */ jsxs10("text", {
26872
27079
  children: [
26873
- /* @__PURE__ */ jsxs7("span", {
27080
+ /* @__PURE__ */ jsxs10("span", {
26874
27081
  fg: tokens.success,
26875
27082
  children: [
26876
27083
  "✓ ",
26877
27084
  prefix
26878
27085
  ]
26879
27086
  }),
26880
- duration3 && /* @__PURE__ */ jsx9("span", {
27087
+ duration3 && /* @__PURE__ */ jsx12("span", {
26881
27088
  fg: tokens.dim,
26882
27089
  children: duration3
26883
27090
  })
26884
27091
  ]
26885
27092
  });
26886
27093
  }
26887
- return /* @__PURE__ */ jsxs7("text", {
27094
+ return /* @__PURE__ */ jsxs10("text", {
26888
27095
  children: [
26889
- /* @__PURE__ */ jsx9("span", {
27096
+ /* @__PURE__ */ jsx12("span", {
26890
27097
  fg: tokens.error,
26891
27098
  children: "✗ "
26892
27099
  }),
26893
- /* @__PURE__ */ jsx9("span", {
27100
+ /* @__PURE__ */ jsx12("span", {
26894
27101
  children: prefix
26895
27102
  }),
26896
- /* @__PURE__ */ jsxs7("span", {
27103
+ /* @__PURE__ */ jsxs10("span", {
26897
27104
  fg: tokens.dim,
26898
27105
  children: [
26899
27106
  " (exit ",
@@ -26906,32 +27113,32 @@ function HistoryEntryLine({ entry }) {
26906
27113
  });
26907
27114
  }
26908
27115
  function HistoryOverlay() {
26909
- const [entries, setEntries] = useState3([]);
26910
- useEffect3(() => {
27116
+ const [entries, setEntries] = useState4([]);
27117
+ useEffect4(() => {
26911
27118
  setEntries(readRecentHistory(500));
26912
27119
  }, []);
26913
- return /* @__PURE__ */ jsxs7(Overlay, {
27120
+ return /* @__PURE__ */ jsxs10(Overlay, {
26914
27121
  title: "Action History",
26915
27122
  borderColor: tokens.border,
26916
27123
  width: 110,
26917
27124
  children: [
26918
- /* @__PURE__ */ jsx9("box", {
27125
+ /* @__PURE__ */ jsx12("box", {
26919
27126
  flexGrow: 1,
26920
27127
  flexDirection: "column",
26921
27128
  style: { minHeight: 30, maxHeight: 46 },
26922
- children: entries.length === 0 ? /* @__PURE__ */ jsx9("text", {
27129
+ children: entries.length === 0 ? /* @__PURE__ */ jsx12("text", {
26923
27130
  fg: tokens.dim,
26924
27131
  children: "No actions recorded yet."
26925
- }) : /* @__PURE__ */ jsx9("scrollbox", {
27132
+ }) : /* @__PURE__ */ jsx12("scrollbox", {
26926
27133
  flexGrow: 1,
26927
- children: entries.map((entry, i2) => /* @__PURE__ */ jsx9(HistoryEntryLine, {
27134
+ children: entries.map((entry, i2) => /* @__PURE__ */ jsx12(HistoryEntryLine, {
26928
27135
  entry
26929
27136
  }, i2))
26930
27137
  })
26931
27138
  }),
26932
- /* @__PURE__ */ jsx9("box", {
27139
+ /* @__PURE__ */ jsx12("box", {
26933
27140
  marginTop: 1,
26934
- children: /* @__PURE__ */ jsx9("text", {
27141
+ children: /* @__PURE__ */ jsx12("text", {
26935
27142
  fg: tokens.dim,
26936
27143
  children: "Press any key to close · newest first"
26937
27144
  })
@@ -26946,28 +27153,28 @@ var init_HistoryOverlay = __esm(() => {
26946
27153
  });
26947
27154
 
26948
27155
  // 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";
27156
+ import { useState as useState5 } from "react";
27157
+ import { jsx as jsx13, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
26951
27158
  function InputModal({ title, placeholder, initialValue, errorMessage: errorMessage2, onSubmit }) {
26952
- const [value, setValue] = useState4(initialValue ?? "");
26953
- return /* @__PURE__ */ jsx10(Overlay, {
27159
+ const [value, setValue] = useState5(initialValue ?? "");
27160
+ return /* @__PURE__ */ jsx13(Overlay, {
26954
27161
  title,
26955
27162
  borderColor: tokens.accent,
26956
- children: /* @__PURE__ */ jsxs8("box", {
27163
+ children: /* @__PURE__ */ jsxs11("box", {
26957
27164
  flexDirection: "column",
26958
27165
  children: [
26959
- /* @__PURE__ */ jsx10("input", {
27166
+ /* @__PURE__ */ jsx13("input", {
26960
27167
  focused: true,
26961
27168
  value: initialValue ?? "",
26962
27169
  placeholder,
26963
27170
  onInput: (val) => setValue(val),
26964
27171
  onSubmit: () => onSubmit(value)
26965
27172
  }),
26966
- errorMessage2 ? /* @__PURE__ */ jsx10("text", {
27173
+ errorMessage2 ? /* @__PURE__ */ jsx13("text", {
26967
27174
  fg: tokens.error,
26968
27175
  style: { marginTop: 1 },
26969
27176
  children: errorMessage2
26970
- }) : /* @__PURE__ */ jsx10("text", {
27177
+ }) : /* @__PURE__ */ jsx13("text", {
26971
27178
  fg: tokens.dim,
26972
27179
  style: { marginTop: 1 },
26973
27180
  children: "Press Enter to submit, empty to cancel"
@@ -26982,23 +27189,23 @@ var init_InputModal = __esm(() => {
26982
27189
  });
26983
27190
 
26984
27191
  // src/tui/components/ChoiceModal.tsx
26985
- import { useState as useState5 } from "react";
27192
+ import { useState as useState6 } from "react";
26986
27193
  import { useKeyboard } from "@opentui/react";
26987
- import { jsx as jsx11, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
27194
+ import { jsx as jsx14, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
26988
27195
  function ChoiceOptionRow({ option, index, isSelected, onSelect }) {
26989
27196
  const tap = useTapHandler(() => onSelect(option.value));
26990
- return /* @__PURE__ */ jsxs9("box", {
27197
+ return /* @__PURE__ */ jsxs12("box", {
26991
27198
  flexDirection: "column",
26992
27199
  backgroundColor: isSelected ? tokens.selectionBg : undefined,
26993
27200
  ...tap,
26994
27201
  children: [
26995
- /* @__PURE__ */ jsx11("text", {
26996
- children: /* @__PURE__ */ jsx11("span", {
27202
+ /* @__PURE__ */ jsx14("text", {
27203
+ children: /* @__PURE__ */ jsx14("span", {
26997
27204
  fg: isSelected ? tokens.bright : tokens.fg,
26998
27205
  children: `${index + 1}. ${option.label}`
26999
27206
  })
27000
27207
  }),
27001
- option.desc && /* @__PURE__ */ jsx11("text", {
27208
+ option.desc && /* @__PURE__ */ jsx14("text", {
27002
27209
  fg: tokens.dim,
27003
27210
  children: ` ${option.desc}`
27004
27211
  })
@@ -27006,7 +27213,7 @@ function ChoiceOptionRow({ option, index, isSelected, onSelect }) {
27006
27213
  });
27007
27214
  }
27008
27215
  function ChoiceModal({ title, options, initialIndex = 0, onSubmit, onCancel }) {
27009
- const [index, setIndex] = useState5(Math.min(Math.max(initialIndex, 0), options.length - 1));
27216
+ const [index, setIndex] = useState6(Math.min(Math.max(initialIndex, 0), options.length - 1));
27010
27217
  useKeyboard((key) => {
27011
27218
  if (key.name === "escape") {
27012
27219
  onCancel();
@@ -27033,19 +27240,19 @@ function ChoiceModal({ title, options, initialIndex = 0, onSubmit, onCancel }) {
27033
27240
  onSubmit(option.value);
27034
27241
  }
27035
27242
  });
27036
- return /* @__PURE__ */ jsx11(Overlay, {
27243
+ return /* @__PURE__ */ jsx14(Overlay, {
27037
27244
  title,
27038
27245
  borderColor: tokens.accent,
27039
- children: /* @__PURE__ */ jsxs9("box", {
27246
+ children: /* @__PURE__ */ jsxs12("box", {
27040
27247
  flexDirection: "column",
27041
27248
  children: [
27042
- options.map((option, i2) => /* @__PURE__ */ jsx11(ChoiceOptionRow, {
27249
+ options.map((option, i2) => /* @__PURE__ */ jsx14(ChoiceOptionRow, {
27043
27250
  option,
27044
27251
  index: i2,
27045
27252
  isSelected: i2 === index,
27046
27253
  onSelect: onSubmit
27047
27254
  }, option.value)),
27048
- /* @__PURE__ */ jsxs9("text", {
27255
+ /* @__PURE__ */ jsxs12("text", {
27049
27256
  fg: tokens.dim,
27050
27257
  style: { marginTop: 1 },
27051
27258
  children: [
@@ -27065,16 +27272,16 @@ var init_ChoiceModal = __esm(() => {
27065
27272
  });
27066
27273
 
27067
27274
  // src/tui/components/ConfigOverlay.tsx
27068
- import { useState as useState6, useEffect as useEffect4 } from "react";
27275
+ import { useState as useState7, useEffect as useEffect5 } from "react";
27069
27276
  import { useKeyboard as useKeyboard2 } from "@opentui/react";
27070
- import { jsx as jsx12, jsxs as jsxs10, Fragment as Fragment2 } from "@opentui/react/jsx-runtime";
27277
+ import { jsx as jsx15, jsxs as jsxs13, Fragment as Fragment2 } from "@opentui/react/jsx-runtime";
27071
27278
  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(() => {
27279
+ const [config2, setConfig] = useState7(null);
27280
+ const [viewState, setViewState] = useState7({ type: "main" });
27281
+ const [selectedIndex, setSelectedIndex] = useState7(1);
27282
+ const [windowStart, setWindowStart] = useState7(0);
27283
+ const [editState, setEditState] = useState7({ type: "none" });
27284
+ useEffect5(() => {
27078
27285
  try {
27079
27286
  setConfig(loadConfig());
27080
27287
  } catch (err) {
@@ -27110,7 +27317,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27110
27317
  detailRows.push({ type: "remove_repo" });
27111
27318
  }
27112
27319
  const activeRows = viewState.type === "main" ? mainRows : detailRows;
27113
- useEffect4(() => {
27320
+ useEffect5(() => {
27114
27321
  if (!activeRows || selectedIndex >= activeRows.length) {
27115
27322
  let newIdx = activeRows.length - 1;
27116
27323
  while (newIdx > 0 && activeRows[newIdx]?.type === "header") {
@@ -27330,17 +27537,17 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27330
27537
  hint = "Remove this repository from config";
27331
27538
  }
27332
27539
  }
27333
- return /* @__PURE__ */ jsxs10(Fragment2, {
27540
+ return /* @__PURE__ */ jsxs13(Fragment2, {
27334
27541
  children: [
27335
- /* @__PURE__ */ jsx12(Overlay, {
27542
+ /* @__PURE__ */ jsx15(Overlay, {
27336
27543
  title: "Configuration",
27337
27544
  borderColor: tokens.border,
27338
- children: /* @__PURE__ */ jsxs10("box", {
27545
+ children: /* @__PURE__ */ jsxs13("box", {
27339
27546
  flexDirection: "column",
27340
27547
  width: "100%",
27341
27548
  height: 24,
27342
27549
  children: [
27343
- /* @__PURE__ */ jsx12("box", {
27550
+ /* @__PURE__ */ jsx15("box", {
27344
27551
  flexDirection: "column",
27345
27552
  flexGrow: 1,
27346
27553
  overflow: "hidden",
@@ -27348,7 +27555,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27348
27555
  const i2 = windowStart + idx;
27349
27556
  const isSelected = i2 === selectedIndex;
27350
27557
  if (row.type === "header") {
27351
- return /* @__PURE__ */ jsx12("text", {
27558
+ return /* @__PURE__ */ jsx15("text", {
27352
27559
  fg: tokens.dim,
27353
27560
  style: { marginTop: i2 === 0 ? 0 : 1 },
27354
27561
  children: row.label
@@ -27377,11 +27584,11 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27377
27584
  labelText = "Remove this repo";
27378
27585
  valText = "";
27379
27586
  }
27380
- return /* @__PURE__ */ jsxs10("box", {
27587
+ return /* @__PURE__ */ jsxs13("box", {
27381
27588
  flexDirection: "row",
27382
27589
  width: "100%",
27383
27590
  children: [
27384
- /* @__PURE__ */ jsxs10("text", {
27591
+ /* @__PURE__ */ jsxs13("text", {
27385
27592
  fg: isSelected ? tokens.bright : tokens.fg,
27386
27593
  bg: isSelected ? tokens.selectionBg : undefined,
27387
27594
  width: 24,
@@ -27390,7 +27597,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27390
27597
  labelText
27391
27598
  ]
27392
27599
  }),
27393
- valText && /* @__PURE__ */ jsx12("text", {
27600
+ valText && /* @__PURE__ */ jsx15("text", {
27394
27601
  fg: isSelected ? tokens.accent : tokens.dim,
27395
27602
  bg: isSelected ? tokens.selectionBg : undefined,
27396
27603
  flexShrink: 1,
@@ -27400,14 +27607,14 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27400
27607
  }, i2);
27401
27608
  })
27402
27609
  }),
27403
- /* @__PURE__ */ jsx12("box", {
27610
+ /* @__PURE__ */ jsx15("box", {
27404
27611
  flexDirection: "row",
27405
27612
  border: true,
27406
27613
  borderColor: tokens.border,
27407
27614
  paddingTop: 1,
27408
27615
  marginTop: 1,
27409
27616
  minHeight: 3,
27410
- children: /* @__PURE__ */ jsx12("text", {
27617
+ children: /* @__PURE__ */ jsx15("text", {
27411
27618
  fg: tokens.dim,
27412
27619
  children: hint
27413
27620
  })
@@ -27415,14 +27622,14 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27415
27622
  ]
27416
27623
  })
27417
27624
  }),
27418
- editState.type === "input" && /* @__PURE__ */ jsx12(InputModal, {
27625
+ editState.type === "input" && /* @__PURE__ */ jsx15(InputModal, {
27419
27626
  title: editState.title,
27420
27627
  initialValue: editState.currentValue,
27421
27628
  placeholder: "Enter value...",
27422
27629
  errorMessage: editState.error,
27423
27630
  onSubmit: handleInputSubmit
27424
27631
  }),
27425
- editState.type === "confirm_remove" && /* @__PURE__ */ jsx12(ConfirmModal, {
27632
+ editState.type === "confirm_remove" && /* @__PURE__ */ jsx15(ConfirmModal, {
27426
27633
  title: "Remove Repo",
27427
27634
  message: `Remove repo ${editState.repo} from config?`
27428
27635
  })
@@ -27439,29 +27646,29 @@ var init_ConfigOverlay = __esm(() => {
27439
27646
  });
27440
27647
 
27441
27648
  // src/tui/components/WarningsOverlay.tsx
27442
- import { jsx as jsx13, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
27649
+ import { jsx as jsx16, jsxs as jsxs14 } from "@opentui/react/jsx-runtime";
27443
27650
  function WarningsOverlay({ warnings, onAcknowledge, onClose }) {
27444
27651
  const ackTap = useTapHandler(() => onAcknowledge?.());
27445
27652
  const closeTap = useTapHandler(() => onClose?.());
27446
- return /* @__PURE__ */ jsxs11(Overlay, {
27653
+ return /* @__PURE__ */ jsxs14(Overlay, {
27447
27654
  title: `Warnings (${warnings.length})`,
27448
27655
  borderColor: tokens.warning,
27449
27656
  width: 80,
27450
27657
  children: [
27451
- /* @__PURE__ */ jsx13("box", {
27658
+ /* @__PURE__ */ jsx16("box", {
27452
27659
  flexDirection: "column",
27453
27660
  style: { maxHeight: 28 },
27454
- children: /* @__PURE__ */ jsx13("scrollbox", {
27661
+ children: /* @__PURE__ */ jsx16("scrollbox", {
27455
27662
  flexGrow: 1,
27456
- children: warnings.map((warning, i2) => /* @__PURE__ */ jsxs11("box", {
27663
+ children: warnings.map((warning, i2) => /* @__PURE__ */ jsxs14("box", {
27457
27664
  flexDirection: "column",
27458
27665
  style: { marginBottom: 1 },
27459
27666
  children: [
27460
- /* @__PURE__ */ jsx13("text", {
27667
+ /* @__PURE__ */ jsx16("text", {
27461
27668
  fg: tokens.warning,
27462
27669
  children: `⚠ ${warning.repoName}`
27463
27670
  }),
27464
- wrapText(warning.message, 72).map((line, j) => /* @__PURE__ */ jsx13("text", {
27671
+ wrapText(warning.message, 72).map((line, j) => /* @__PURE__ */ jsx16("text", {
27465
27672
  fg: tokens.fg,
27466
27673
  children: ` ${line}`
27467
27674
  }, j))
@@ -27469,36 +27676,36 @@ function WarningsOverlay({ warnings, onAcknowledge, onClose }) {
27469
27676
  }, i2))
27470
27677
  })
27471
27678
  }),
27472
- /* @__PURE__ */ jsxs11("box", {
27679
+ /* @__PURE__ */ jsxs14("box", {
27473
27680
  flexDirection: "row",
27474
27681
  gap: 3,
27475
27682
  style: { marginTop: 2 },
27476
27683
  justifyContent: "center",
27477
27684
  children: [
27478
- /* @__PURE__ */ jsx13("box", {
27685
+ /* @__PURE__ */ jsx16("box", {
27479
27686
  ...ackTap,
27480
- children: /* @__PURE__ */ jsxs11("text", {
27687
+ children: /* @__PURE__ */ jsxs14("text", {
27481
27688
  children: [
27482
- /* @__PURE__ */ jsx13("span", {
27689
+ /* @__PURE__ */ jsx16("span", {
27483
27690
  fg: tokens.dim,
27484
27691
  children: "[a] "
27485
27692
  }),
27486
- /* @__PURE__ */ jsx13("span", {
27693
+ /* @__PURE__ */ jsx16("span", {
27487
27694
  fg: tokens.success,
27488
27695
  children: "Acknowledge"
27489
27696
  })
27490
27697
  ]
27491
27698
  })
27492
27699
  }),
27493
- /* @__PURE__ */ jsx13("box", {
27700
+ /* @__PURE__ */ jsx16("box", {
27494
27701
  ...closeTap,
27495
- children: /* @__PURE__ */ jsxs11("text", {
27702
+ children: /* @__PURE__ */ jsxs14("text", {
27496
27703
  children: [
27497
- /* @__PURE__ */ jsx13("span", {
27704
+ /* @__PURE__ */ jsx16("span", {
27498
27705
  fg: tokens.dim,
27499
27706
  children: "[esc] "
27500
27707
  }),
27501
- /* @__PURE__ */ jsx13("span", {
27708
+ /* @__PURE__ */ jsx16("span", {
27502
27709
  children: "Close"
27503
27710
  })
27504
27711
  ]
@@ -27506,7 +27713,7 @@ function WarningsOverlay({ warnings, onAcknowledge, onClose }) {
27506
27713
  })
27507
27714
  ]
27508
27715
  }),
27509
- /* @__PURE__ */ jsx13("text", {
27716
+ /* @__PURE__ */ jsx16("text", {
27510
27717
  style: { marginTop: 1, fg: tokens.dim },
27511
27718
  children: "Press a to acknowledge and clear errors"
27512
27719
  })
@@ -27521,10 +27728,10 @@ var init_WarningsOverlay = __esm(() => {
27521
27728
  });
27522
27729
 
27523
27730
  // src/tui/hooks/useSpinnerFrame.ts
27524
- import { useEffect as useEffect5, useState as useState7 } from "react";
27731
+ import { useEffect as useEffect6, useState as useState8 } from "react";
27525
27732
  function useSpinnerFrame(active) {
27526
- const [index, setIndex] = useState7(0);
27527
- useEffect5(() => {
27733
+ const [index, setIndex] = useState8(0);
27734
+ useEffect6(() => {
27528
27735
  if (!active)
27529
27736
  return;
27530
27737
  const id = setInterval(() => setIndex((i2) => (i2 + 1) % FRAMES.length), INTERVAL_MS);
@@ -27537,11 +27744,393 @@ var init_useSpinnerFrame = __esm(() => {
27537
27744
  FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
27538
27745
  });
27539
27746
 
27747
+ // src/tui/hooks/useTerminalSessions.ts
27748
+ import { useState as useState9, useCallback as useCallback5, useRef as useRef6, useEffect as useEffect7 } from "react";
27749
+ function worktreeKeyFor(repoName, branch, path37) {
27750
+ return path37 || `${repoName}:${branch}`;
27751
+ }
27752
+ function nextTerminalSessionLabel(existingCount) {
27753
+ return `Session ${existingCount + 1}`;
27754
+ }
27755
+ function isSameSize(session, cols, rows) {
27756
+ return session.cols === cols && session.rows === rows;
27757
+ }
27758
+ function updateSessionSize(session, cols, rows) {
27759
+ return isSameSize(session, cols, rows) ? session : { ...session, cols, rows };
27760
+ }
27761
+ function relabelTerminalSessions(sessions) {
27762
+ return sessions.map((session, index) => ({ ...session, label: nextTerminalSessionLabel(index) }));
27763
+ }
27764
+ function useTerminalSessions() {
27765
+ const [sessionsByKey, setSessionsByKey] = useState9(() => new Map);
27766
+ const procsRef = useRef6(new Map);
27767
+ const listenersRef = useRef6(new Map);
27768
+ const rawBuffersRef = useRef6(new Map);
27769
+ const getSessions = useCallback5((repoName, branch, path37) => {
27770
+ const key = worktreeKeyFor(repoName, branch, path37);
27771
+ return sessionsByKey.get(key) ?? [];
27772
+ }, [sessionsByKey]);
27773
+ const getKey = useCallback5((repoName, branch, path37) => worktreeKeyFor(repoName, branch, path37), []);
27774
+ const createSession = useCallback5((repoName, branch, path37, opts) => {
27775
+ const key = worktreeKeyFor(repoName, branch, path37);
27776
+ const existing = sessionsByKey.get(key) ?? [];
27777
+ if (existing.length >= MAX_TERMINAL_SESSIONS) {
27778
+ return { session: null, error: `Max ${MAX_TERMINAL_SESSIONS} sessions per worktree` };
27779
+ }
27780
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
27781
+ const label = nextTerminalSessionLabel(existing.length);
27782
+ const cols = opts?.cols ?? 80;
27783
+ const rows = opts?.rows ?? 24;
27784
+ const session = {
27785
+ id,
27786
+ label,
27787
+ worktreeKey: key,
27788
+ worktreePath: path37,
27789
+ repoName,
27790
+ branch,
27791
+ lines: [],
27792
+ inputBuffer: "",
27793
+ exited: null,
27794
+ proc: null,
27795
+ terminal: null,
27796
+ cols,
27797
+ rows,
27798
+ usePty: false
27799
+ };
27800
+ const shell = process.env.SHELL || "/bin/bash";
27801
+ const appendPipe = (data) => {
27802
+ const text = new TextDecoder().decode(data);
27803
+ const parts = text.split(`
27804
+ `);
27805
+ setSessionsByKey((prev) => {
27806
+ const next = new Map(prev);
27807
+ const list = next.get(key);
27808
+ if (!list)
27809
+ return prev;
27810
+ const idx = list.findIndex((s) => s.id === id);
27811
+ if (idx === -1)
27812
+ return prev;
27813
+ const updated = [...list];
27814
+ const sess = updated[idx];
27815
+ const newLines = [...sess.lines];
27816
+ for (let i2 = 0;i2 < parts.length; i2++) {
27817
+ const part = parts[i2];
27818
+ if (i2 === 0 && newLines.length > 0) {
27819
+ if (part)
27820
+ newLines[newLines.length - 1] = (newLines[newLines.length - 1] ?? "") + part;
27821
+ } else {
27822
+ if (part || i2 < parts.length - 1)
27823
+ newLines.push(part);
27824
+ }
27825
+ }
27826
+ if (newLines.length > 1000)
27827
+ newLines.splice(0, newLines.length - 1000);
27828
+ updated[idx] = { ...sess, lines: newLines };
27829
+ next.set(key, updated);
27830
+ return next;
27831
+ });
27832
+ };
27833
+ const onPtyData = (data) => {
27834
+ const buf = rawBuffersRef.current.get(id) ?? [];
27835
+ buf.push(data);
27836
+ if (buf.length > 500)
27837
+ buf.shift();
27838
+ rawBuffersRef.current.set(id, buf);
27839
+ const listener = listenersRef.current.get(id);
27840
+ if (listener) {
27841
+ listener(data);
27842
+ return;
27843
+ }
27844
+ };
27845
+ const onExit2 = (code) => {
27846
+ setSessionsByKey((prev) => {
27847
+ const next = new Map(prev);
27848
+ const list = next.get(key);
27849
+ if (!list)
27850
+ return prev;
27851
+ const idx = list.findIndex((s) => s.id === id);
27852
+ if (idx === -1)
27853
+ return prev;
27854
+ const updated = [...list];
27855
+ updated[idx] = { ...updated[idx], exited: code ?? 0, proc: null, terminal: null };
27856
+ next.set(key, updated);
27857
+ return next;
27858
+ });
27859
+ procsRef.current.delete(id);
27860
+ listenersRef.current.delete(id);
27861
+ };
27862
+ let usePty = false;
27863
+ try {
27864
+ if (typeof Bun !== "undefined" && typeof Bun.spawn === "function") {
27865
+ try {
27866
+ const proc = Bun.spawn([shell], {
27867
+ cwd: path37 || process.cwd(),
27868
+ env: { ...process.env, TERM: "xterm-256color", COLORTERM: "truecolor", WTX_SESSION: label },
27869
+ terminal: {
27870
+ cols,
27871
+ rows,
27872
+ name: "xterm-256color",
27873
+ data(_terminal, data) {
27874
+ onPtyData(data);
27875
+ },
27876
+ exit(_terminal, exitCode) {
27877
+ onExit2(exitCode);
27878
+ }
27879
+ }
27880
+ });
27881
+ if (proc && proc.terminal) {
27882
+ session.proc = proc;
27883
+ session.terminal = proc.terminal;
27884
+ session.usePty = true;
27885
+ usePty = true;
27886
+ procsRef.current.set(id, proc);
27887
+ (async () => {
27888
+ try {
27889
+ const code = await proc.exited;
27890
+ onExit2(code);
27891
+ } catch {}
27892
+ })();
27893
+ }
27894
+ } catch {}
27895
+ }
27896
+ if (!usePty)
27897
+ throw new Error("pty not available");
27898
+ } catch {
27899
+ try {
27900
+ const proc = Bun.spawn([shell], {
27901
+ cwd: path37 || process.cwd(),
27902
+ stdin: "pipe",
27903
+ stdout: "pipe",
27904
+ stderr: "pipe",
27905
+ env: { ...process.env, TERM: "xterm-256color", WTX_SESSION: label }
27906
+ });
27907
+ session.proc = proc;
27908
+ session.usePty = false;
27909
+ procsRef.current.set(id, proc);
27910
+ (async () => {
27911
+ const stdout = proc.stdout;
27912
+ if (stdout) {
27913
+ const reader = stdout.getReader();
27914
+ try {
27915
+ while (true) {
27916
+ const { value, done } = await reader.read();
27917
+ if (done)
27918
+ break;
27919
+ if (value)
27920
+ appendPipe(value);
27921
+ }
27922
+ } catch {}
27923
+ }
27924
+ })();
27925
+ (async () => {
27926
+ const stderr = proc.stderr;
27927
+ if (stderr) {
27928
+ const reader = stderr.getReader();
27929
+ try {
27930
+ while (true) {
27931
+ const { value, done } = await reader.read();
27932
+ if (done)
27933
+ break;
27934
+ if (value)
27935
+ appendPipe(value);
27936
+ }
27937
+ } catch {}
27938
+ }
27939
+ })();
27940
+ (async () => {
27941
+ try {
27942
+ const code = await proc.exited;
27943
+ onExit2(code);
27944
+ } catch {}
27945
+ })();
27946
+ } catch (e) {
27947
+ session.lines = [`Failed to spawn shell: ${e.message}`];
27948
+ }
27949
+ }
27950
+ if (session.usePty) {
27951
+ session.lines = [`${label} — ${path37} (${shell}) [pty]`, ""];
27952
+ } else if (session.lines.length === 0) {
27953
+ session.lines = [`${label} — ${path37} (${shell})`, ""];
27954
+ }
27955
+ setSessionsByKey((prev) => {
27956
+ const next = new Map(prev);
27957
+ const list = next.get(key) ?? [];
27958
+ next.set(key, [...list, session]);
27959
+ return next;
27960
+ });
27961
+ return { session };
27962
+ }, [sessionsByKey]);
27963
+ const removeSession = useCallback5((repoName, branch, path37, id) => {
27964
+ const key = worktreeKeyFor(repoName, branch, path37);
27965
+ setSessionsByKey((prev) => {
27966
+ const next = new Map(prev);
27967
+ const list = next.get(key);
27968
+ if (!list)
27969
+ return prev;
27970
+ const sess = list.find((s) => s.id === id);
27971
+ if (sess?.proc) {
27972
+ try {
27973
+ if (sess.terminal) {
27974
+ try {
27975
+ sess.terminal.close();
27976
+ } catch {}
27977
+ }
27978
+ sess.proc.kill();
27979
+ } catch {}
27980
+ procsRef.current.delete(id);
27981
+ listenersRef.current.delete(id);
27982
+ rawBuffersRef.current.delete(id);
27983
+ }
27984
+ const filtered = list.filter((s) => s.id !== id);
27985
+ const relabeled = relabelTerminalSessions(filtered);
27986
+ if (relabeled.length === 0) {
27987
+ next.delete(key);
27988
+ } else {
27989
+ next.set(key, relabeled);
27990
+ }
27991
+ return next;
27992
+ });
27993
+ }, []);
27994
+ const sendInput = useCallback5((repoName, branch, path37, id, data) => {
27995
+ const key = worktreeKeyFor(repoName, branch, path37);
27996
+ const list = sessionsByKey.get(key) ?? [];
27997
+ const sess = list.find((s) => s.id === id);
27998
+ if (!sess?.proc)
27999
+ return;
28000
+ try {
28001
+ if (sess.usePty && sess.terminal) {
28002
+ if (typeof data === "string") {
28003
+ sess.terminal.write(new TextEncoder().encode(data));
28004
+ } else {
28005
+ sess.terminal.write(data);
28006
+ }
28007
+ return;
28008
+ }
28009
+ const proc = sess.proc;
28010
+ const str = typeof data === "string" ? data : new TextDecoder().decode(data);
28011
+ if (proc.stdin && typeof proc.stdin.write === "function") {
28012
+ proc.stdin.write(str);
28013
+ } else if (proc.stdin instanceof WritableStream) {
28014
+ const writer = proc.stdin.getWriter();
28015
+ writer.write(new TextEncoder().encode(str));
28016
+ writer.releaseLock();
28017
+ } else {
28018
+ proc.stdin?.write?.(str);
28019
+ }
28020
+ } catch {}
28021
+ if (!sess.usePty) {
28022
+ const str = typeof data === "string" ? data : new TextDecoder().decode(data);
28023
+ if (str.endsWith(`
28024
+ `)) {
28025
+ const cmd = str.slice(0, -1);
28026
+ if (cmd) {
28027
+ setSessionsByKey((prev) => {
28028
+ const next = new Map(prev);
28029
+ const l = next.get(key);
28030
+ if (!l)
28031
+ return prev;
28032
+ const idx = l.findIndex((s2) => s2.id === id);
28033
+ if (idx === -1)
28034
+ return prev;
28035
+ const updated = [...l];
28036
+ const s = updated[idx];
28037
+ updated[idx] = { ...s, lines: [...s.lines, `$ ${cmd}`] };
28038
+ next.set(key, updated);
28039
+ return next;
28040
+ });
28041
+ }
28042
+ }
28043
+ }
28044
+ }, [sessionsByKey]);
28045
+ const resizeSession = useCallback5((repoName, branch, path37, id, cols, rows) => {
28046
+ const key = worktreeKeyFor(repoName, branch, path37);
28047
+ setSessionsByKey((prev) => {
28048
+ const list = prev.get(key);
28049
+ if (!list)
28050
+ return prev;
28051
+ const idx = list.findIndex((s) => s.id === id);
28052
+ if (idx === -1)
28053
+ return prev;
28054
+ const sess = list[idx];
28055
+ if (isSameSize(sess, cols, rows))
28056
+ return prev;
28057
+ try {
28058
+ sess.terminal?.resize?.(cols, rows);
28059
+ } catch {}
28060
+ const next = new Map(prev);
28061
+ const updated = [...list];
28062
+ updated[idx] = updateSessionSize(sess, cols, rows);
28063
+ next.set(key, updated);
28064
+ return next;
28065
+ });
28066
+ }, []);
28067
+ const registerListener = useCallback5((id, fn) => {
28068
+ listenersRef.current.set(id, fn);
28069
+ const buf = rawBuffersRef.current.get(id);
28070
+ if (buf) {
28071
+ for (const chunk of buf) {
28072
+ try {
28073
+ fn(chunk);
28074
+ } catch {}
28075
+ }
28076
+ }
28077
+ }, []);
28078
+ const unregisterListener = useCallback5((id) => {
28079
+ listenersRef.current.delete(id);
28080
+ }, []);
28081
+ const cleanupAll = useCallback5(() => {
28082
+ for (const proc of procsRef.current.values()) {
28083
+ try {
28084
+ if (proc.terminal) {
28085
+ try {
28086
+ proc.terminal.close();
28087
+ } catch {}
28088
+ }
28089
+ proc.kill();
28090
+ } catch {}
28091
+ }
28092
+ procsRef.current.clear();
28093
+ listenersRef.current.clear();
28094
+ rawBuffersRef.current.clear();
28095
+ }, []);
28096
+ useEffect7(() => {
28097
+ return () => cleanupAll();
28098
+ }, [cleanupAll]);
28099
+ const pruneKey = useCallback5((key) => {
28100
+ setSessionsByKey((prev) => {
28101
+ const next = new Map(prev);
28102
+ const list = next.get(key);
28103
+ if (!list)
28104
+ return prev;
28105
+ for (const s of list) {
28106
+ if (s.proc) {
28107
+ try {
28108
+ if (s.terminal) {
28109
+ try {
28110
+ s.terminal.close();
28111
+ } catch {}
28112
+ }
28113
+ s.proc.kill();
28114
+ } catch {}
28115
+ procsRef.current.delete(s.id);
28116
+ listenersRef.current.delete(s.id);
28117
+ rawBuffersRef.current.delete(s.id);
28118
+ }
28119
+ }
28120
+ next.delete(key);
28121
+ return next;
28122
+ });
28123
+ }, []);
28124
+ return { sessionsByKey, getSessions, getKey, createSession, removeSession, sendInput, resizeSession, registerListener, unregisterListener, pruneKey, cleanupAll };
28125
+ }
28126
+ var MAX_TERMINAL_SESSIONS = 5;
28127
+ var init_useTerminalSessions = () => {};
28128
+
27540
28129
  // src/tui/components/App.tsx
27541
- import { useState as useState8, useEffect as useEffect6, useMemo, useRef as useRef5, useCallback as useCallback4 } from "react";
28130
+ import { useState as useState10, useEffect as useEffect8, useMemo, useRef as useRef7, useCallback as useCallback6 } from "react";
27542
28131
  import { existsSync as existsSync4 } from "node:fs";
27543
28132
  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";
28133
+ import { jsx as jsx17, jsxs as jsxs15 } from "@opentui/react/jsx-runtime";
27545
28134
  function getWorktreePathFor(repoName, branch) {
27546
28135
  try {
27547
28136
  const config2 = loadConfig();
@@ -27554,37 +28143,51 @@ function getWorktreePathFor(repoName, branch) {
27554
28143
  function App({ opts }) {
27555
28144
  const renderer = useRenderer2();
27556
28145
  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) => {
28146
+ const [selectedIndex, setSelectedIndex] = useState10(0);
28147
+ const [modal, setModal] = useState10({ type: "none" });
28148
+ const [actionMessage, setActionMessage] = useState10();
28149
+ const messageTimer = useRef7(null);
28150
+ const [ops, setOps] = useState10([]);
28151
+ const [failedLogs, setFailedLogs] = useState10([]);
28152
+ const nextOpId = useRef7(1);
28153
+ const [createModal, setCreateModal] = useState10(false);
28154
+ const [createError, setCreateError] = useState10();
28155
+ const [createBaseModal, setCreateBaseModal] = useState10(null);
28156
+ const [createBaseError, setCreateBaseError] = useState10();
28157
+ const [createDepsChoice, setCreateDepsChoice] = useState10(null);
28158
+ const [pullPrModal, setPullPrModal] = useState10(false);
28159
+ const [pullPrError, setPullPrError] = useState10();
28160
+ const [pullForceChoice, setPullForceChoice] = useState10(null);
28161
+ const [renameModal, setRenameModal] = useState10(false);
28162
+ const [renameError, setRenameError] = useState10();
28163
+ const [configOpen, setConfigOpen] = useState10(false);
28164
+ const [refreshScopes, setRefreshScopes] = useState10([]);
28165
+ const [filterText, setFilterText] = useState10("");
28166
+ const [isFiltering, setIsFiltering] = useState10(false);
28167
+ const [selection, setSelection] = useState10(new Set);
28168
+ const [splitRatio, setSplitRatio] = useState10(() => {
28169
+ try {
28170
+ const cfg = loadConfig();
28171
+ const left = cfg.tui?.leftPaneWidthWeight ?? 3;
28172
+ const right = cfg.tui?.rightPaneWidthWeight ?? 7;
28173
+ const total = left + right;
28174
+ if (total > 0)
28175
+ return left / total;
28176
+ } catch {}
28177
+ return 3 / 10;
28178
+ });
28179
+ const [isResizing, setIsResizing] = useState10(false);
28180
+ const terminalSessions = useTerminalSessions();
28181
+ const [activeTabByWorktree, setActiveTabByWorktree] = useState10(() => new Map);
28182
+ const [terminalFocused, setTerminalFocused] = useState10(false);
28183
+ const { width: termW, height: termH } = useTerminalDimensions();
28184
+ const totalWidth = termW || renderer.width || 80;
28185
+ const totalHeight = termH || renderer.height || 24;
28186
+ useEffect8(() => {
28187
+ if (termW)
28188
+ setSplitRatio((prev) => clampSplitRatio(termW, prev));
28189
+ }, [termW]);
28190
+ const doRefresh = useCallback6(async (scope) => {
27588
28191
  const targets = scope ?? [
27589
28192
  ...new Set([...blocks.map((b) => b.repoName), ...pendingRepos])
27590
28193
  ];
@@ -27595,18 +28198,18 @@ function App({ opts }) {
27595
28198
  setRefreshScopes([]);
27596
28199
  }
27597
28200
  }, [blocks, pendingRepos, refresh]);
27598
- useEffect6(() => {
28201
+ useEffect8(() => {
27599
28202
  if (error52) {
27600
28203
  setModal({ type: "error", message: error52 });
27601
28204
  }
27602
28205
  }, [error52]);
27603
- const flash = useCallback4((message, ms = 3000) => {
28206
+ const flash = useCallback6((message, ms = 3000) => {
27604
28207
  if (messageTimer.current)
27605
28208
  clearTimeout(messageTimer.current);
27606
28209
  setActionMessage(message);
27607
28210
  messageTimer.current = setTimeout(() => setActionMessage(undefined), ms);
27608
28211
  }, []);
27609
- const copySelectedText = useCallback4((warnOnEmpty) => {
28212
+ const copySelectedText = useCallback6((warnOnEmpty) => {
27610
28213
  if (isResizing) {
27611
28214
  renderer.clearSelection();
27612
28215
  return;
@@ -27636,12 +28239,130 @@ function App({ opts }) {
27636
28239
  const flatRows = useMemo(() => displayBlocks.flatMap((b) => b.rows.filter((r) => !r.isPendingCreate)), [displayBlocks]);
27637
28240
  const totalRows = blocks.flatMap((b) => b.rows).length;
27638
28241
  const maxIndex = Math.max(0, flatRows.length - 1);
27639
- useEffect6(() => {
28242
+ useEffect8(() => {
27640
28243
  if (selectedIndex > maxIndex && maxIndex >= 0) {
27641
28244
  setSelectedIndex(maxIndex);
27642
28245
  }
27643
28246
  }, [maxIndex, selectedIndex]);
27644
28247
  const selectedRow = flatRows[selectedIndex] ?? null;
28248
+ const worktreeKey = selectedRow ? terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path) : "";
28249
+ const sessionsForSelected = selectedRow ? terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path) : [];
28250
+ const activeTabId = worktreeKey ? activeTabByWorktree.get(worktreeKey) ?? "details" : "details";
28251
+ useEffect8(() => {
28252
+ if (worktreeKey && !activeTabByWorktree.has(worktreeKey)) {
28253
+ setActiveTabByWorktree((prev) => {
28254
+ if (prev.has(worktreeKey))
28255
+ return prev;
28256
+ const next = new Map(prev);
28257
+ next.set(worktreeKey, "details");
28258
+ return next;
28259
+ });
28260
+ }
28261
+ }, [worktreeKey, activeTabByWorktree]);
28262
+ useEffect8(() => {
28263
+ if (sessionsForSelected.length === 0 && activeTabId !== "details") {
28264
+ setActiveTabByWorktree((prev) => {
28265
+ const next = new Map(prev);
28266
+ next.set(worktreeKey, "details");
28267
+ return next;
28268
+ });
28269
+ setTerminalFocused(false);
28270
+ } else if (sessionsForSelected.length > 0) {
28271
+ const exists = sessionsForSelected.some((s) => s.id === activeTabId) || activeTabId === "details";
28272
+ if (!exists) {
28273
+ setActiveTabByWorktree((prev) => {
28274
+ const next = new Map(prev);
28275
+ next.set(worktreeKey, sessionsForSelected[0].id);
28276
+ return next;
28277
+ });
28278
+ }
28279
+ }
28280
+ }, [sessionsForSelected, activeTabId, worktreeKey]);
28281
+ const handleSelectTab = useCallback6((id) => {
28282
+ if (!worktreeKey)
28283
+ return;
28284
+ setActiveTabByWorktree((prev) => {
28285
+ const next = new Map(prev);
28286
+ next.set(worktreeKey, id);
28287
+ return next;
28288
+ });
28289
+ const isSession = sessionsForSelected.some((s) => s.id === id);
28290
+ setTerminalFocused(isSession);
28291
+ }, [worktreeKey, sessionsForSelected]);
28292
+ const handleAddSession = useCallback6(() => {
28293
+ if (!selectedRow) {
28294
+ flash("No worktree selected");
28295
+ return;
28296
+ }
28297
+ const rawLeftTmp = Math.floor(totalWidth * splitRatio);
28298
+ const leftColsTmp = Math.max(20, Math.min(totalWidth - 20 - DIVIDER_WIDTH, rawLeftTmp));
28299
+ const rightColsTmp = Math.max(20, totalWidth - leftColsTmp - DIVIDER_WIDTH);
28300
+ const cols = Math.max(20, rightColsTmp - 4);
28301
+ const rows = Math.max(10, totalHeight - 10);
28302
+ const { session, error: error53 } = terminalSessions.createSession(selectedRow.repoName, selectedRow.branch, selectedRow.path, { cols, rows });
28303
+ if (error53 || !session) {
28304
+ flash(error53 ?? "Failed to create session");
28305
+ return;
28306
+ }
28307
+ const key = terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28308
+ setActiveTabByWorktree((prev) => {
28309
+ const next = new Map(prev);
28310
+ next.set(key, session.id);
28311
+ return next;
28312
+ });
28313
+ setTerminalFocused(true);
28314
+ flash(`Session ${session.label} created`, 2000);
28315
+ }, [selectedRow, terminalSessions, flash, totalWidth, splitRatio, totalHeight]);
28316
+ const handleCloseSession = useCallback6((id) => {
28317
+ if (!selectedRow)
28318
+ return;
28319
+ terminalSessions.removeSession(selectedRow.repoName, selectedRow.branch, selectedRow.path, id);
28320
+ const key = terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28321
+ setActiveTabByWorktree((prev) => {
28322
+ const next = new Map(prev);
28323
+ const current = next.get(key);
28324
+ if (current === id)
28325
+ next.set(key, "details");
28326
+ return next;
28327
+ });
28328
+ setTerminalFocused(false);
28329
+ flash("Session closed", 2000);
28330
+ }, [selectedRow, terminalSessions, flash]);
28331
+ useEffect8(() => {
28332
+ const existingKeys = new Set(blocks.flatMap((b) => b.rows.map((r) => terminalSessions.getKey(r.repoName, r.branch, r.path))));
28333
+ for (const key of terminalSessions.sessionsByKey.keys()) {
28334
+ if (!existingKeys.has(key))
28335
+ terminalSessions.pruneKey(key);
28336
+ }
28337
+ }, [blocks, terminalSessions]);
28338
+ const tabsForSelected = useMemo(() => {
28339
+ const base2 = [
28340
+ {
28341
+ id: "details",
28342
+ label: "Details",
28343
+ render: ({ worktree }) => /* @__PURE__ */ jsx17(DetailsContent, {
28344
+ selectedRow: worktree
28345
+ })
28346
+ }
28347
+ ];
28348
+ for (const s of sessionsForSelected) {
28349
+ base2.push({
28350
+ id: s.id,
28351
+ label: s.label,
28352
+ closable: true,
28353
+ render: () => /* @__PURE__ */ jsx17(TerminalView, {
28354
+ session: s,
28355
+ focused: terminalFocused && activeTabId === s.id,
28356
+ onFocus: () => setTerminalFocused(true),
28357
+ onSend: (data) => terminalSessions.sendInput(s.repoName, s.branch, s.worktreePath, s.id, data),
28358
+ onResize: (cols, rows) => terminalSessions.resizeSession(s.repoName, s.branch, s.worktreePath, s.id, cols, rows),
28359
+ registerListener: terminalSessions.registerListener,
28360
+ unregisterListener: terminalSessions.unregisterListener
28361
+ })
28362
+ });
28363
+ }
28364
+ return base2;
28365
+ }, [sessionsForSelected, terminalFocused, activeTabId, terminalSessions]);
27645
28366
  const { repoVerbs, rowVerbs } = useMemo(() => {
27646
28367
  const rv = new Map;
27647
28368
  const nv = new Map;
@@ -27780,7 +28501,7 @@ function App({ opts }) {
27780
28501
  args.push("--force");
27781
28502
  executeOp(op, args, [repoName]);
27782
28503
  };
27783
- const handleHintClick = useCallback4((key) => {
28504
+ const handleHintClick = useCallback6((key) => {
27784
28505
  if (key === "c") {
27785
28506
  setConfigOpen(true);
27786
28507
  return;
@@ -27932,8 +28653,72 @@ function App({ opts }) {
27932
28653
  setModal({ type: "confirm_sync", rows: targets });
27933
28654
  return;
27934
28655
  }
27935
- }, [selectedRow, selection, warnings, busyRepos, busyRowPaths, doRefresh, flash, blocks]);
28656
+ if (key === "t") {
28657
+ if (!selectedRow)
28658
+ return;
28659
+ const sessions = terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28660
+ if (sessions.length >= MAX_TERMINAL_SESSIONS) {
28661
+ flash(`Max ${MAX_TERMINAL_SESSIONS} sessions per worktree`);
28662
+ return;
28663
+ }
28664
+ handleAddSession();
28665
+ return;
28666
+ }
28667
+ }, [selectedRow, selection, warnings, busyRepos, busyRowPaths, doRefresh, flash, blocks, terminalSessions]);
27936
28668
  useKeyboard3((key) => {
28669
+ if (terminalFocused) {
28670
+ if (key.ctrl && key.name === "g") {
28671
+ setTerminalFocused(false);
28672
+ return;
28673
+ }
28674
+ const activeSession = sessionsForSelected.find((s) => s.id === activeTabId);
28675
+ if (activeSession) {
28676
+ let data = null;
28677
+ const seq = key.sequence;
28678
+ const raw = key.raw;
28679
+ if (key.ctrl && key.name && key.name.length === 1) {
28680
+ const code = key.name.charCodeAt(0) - 96;
28681
+ if (code >= 1 && code <= 26)
28682
+ data = String.fromCharCode(code);
28683
+ else if (seq)
28684
+ data = seq;
28685
+ else if (raw)
28686
+ data = raw;
28687
+ } else if (seq) {
28688
+ data = seq;
28689
+ } else if (raw) {
28690
+ data = raw;
28691
+ } else if (key.name === "return" || key.name === "enter") {
28692
+ data = "\r";
28693
+ } else if (key.name === "backspace") {
28694
+ data = "";
28695
+ } else if (key.name === "tab") {
28696
+ data = "\t";
28697
+ } else if (key.name === "escape") {
28698
+ data = "\x1B";
28699
+ } else if (key.name && key.name.length === 1) {
28700
+ data = key.name;
28701
+ }
28702
+ if (data !== null) {
28703
+ terminalSessions.sendInput(activeSession.repoName, activeSession.branch, activeSession.worktreePath, activeSession.id, data);
28704
+ }
28705
+ }
28706
+ return;
28707
+ }
28708
+ if (key.ctrl && key.name === "g") {
28709
+ if (sessionsForSelected.length > 0) {
28710
+ const targetId = activeTabId !== "details" ? activeTabId : sessionsForSelected[0].id;
28711
+ if (worktreeKey) {
28712
+ setActiveTabByWorktree((prev) => {
28713
+ const next = new Map(prev);
28714
+ next.set(worktreeKey, targetId);
28715
+ return next;
28716
+ });
28717
+ }
28718
+ setTerminalFocused(true);
28719
+ return;
28720
+ }
28721
+ }
27937
28722
  if (isFiltering) {
27938
28723
  if (key.name === "escape") {
27939
28724
  setIsFiltering(false);
@@ -28245,108 +29030,106 @@ function App({ opts }) {
28245
29030
  setModal({ type: "confirm_sync", rows: targets });
28246
29031
  return;
28247
29032
  }
28248
- if (key.name === "a") {
28249
- const targets = getSelectedRows();
28250
- if (targets.length === 0)
28251
- return;
28252
- const target = targets[0];
28253
- if (!target)
29033
+ if (key.name === "t") {
29034
+ if (!selectedRow) {
29035
+ flash("No worktree selected");
28254
29036
  return;
28255
- const conflict = findConflict([target]);
28256
- if (conflict) {
28257
- flash(conflict);
29037
+ }
29038
+ const sessions = terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path);
29039
+ if (sessions.length >= MAX_TERMINAL_SESSIONS) {
29040
+ flash(`Max ${MAX_TERMINAL_SESSIONS} sessions per worktree`);
28258
29041
  return;
28259
29042
  }
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
- })();
29043
+ handleAddSession();
29044
+ return;
28287
29045
  }
28288
29046
  });
28289
29047
  const rawLeft = Math.floor(totalWidth * splitRatio);
28290
29048
  const leftCols = Math.max(20, Math.min(totalWidth - 20 - DIVIDER_WIDTH, rawLeft));
28291
29049
  const rightCols = Math.max(20, totalWidth - leftCols - DIVIDER_WIDTH);
28292
- return /* @__PURE__ */ jsxs12("box", {
29050
+ const paneCols = Math.max(20, rightCols - 4);
29051
+ const paneRows = Math.max(10, totalHeight - 10);
29052
+ useEffect8(() => {
29053
+ for (const sessions of terminalSessions.sessionsByKey.values()) {
29054
+ for (const s of sessions) {
29055
+ if (s.usePty && s.terminal && (s.cols !== paneCols || s.rows !== paneRows)) {
29056
+ terminalSessions.resizeSession(s.repoName, s.branch, s.worktreePath, s.id, paneCols, paneRows);
29057
+ }
29058
+ }
29059
+ }
29060
+ }, [paneCols, paneRows, terminalSessions]);
29061
+ useEffect8(() => {
29062
+ setTerminalFocused(false);
29063
+ }, [selectedIndex]);
29064
+ return /* @__PURE__ */ jsxs15("box", {
28293
29065
  flexDirection: "column",
28294
29066
  width: "100%",
28295
29067
  height: "100%",
28296
29068
  children: [
28297
- /* @__PURE__ */ jsxs12("box", {
29069
+ /* @__PURE__ */ jsxs15("box", {
28298
29070
  flexDirection: "row",
28299
29071
  width: "100%",
28300
29072
  flexGrow: 1,
28301
29073
  children: [
28302
- /* @__PURE__ */ jsx14("box", {
29074
+ /* @__PURE__ */ jsx17("box", {
28303
29075
  width: leftCols,
28304
29076
  height: "100%",
28305
29077
  flexDirection: "column",
28306
- children: /* @__PURE__ */ jsx14(WorktreeTable, {
29078
+ onMouseDown: () => setTerminalFocused(false),
29079
+ children: /* @__PURE__ */ jsx17(WorktreeTable, {
28307
29080
  blocks: displayBlocks,
28308
29081
  selectedIndex,
28309
29082
  selection,
28310
29083
  frame: spinnerFrame,
28311
29084
  repoVerbs,
28312
29085
  rowVerbs,
28313
- onRowClick: (idx) => setSelectedIndex(idx),
29086
+ onRowClick: (idx) => {
29087
+ setTerminalFocused(false);
29088
+ setSelectedIndex(idx);
29089
+ },
28314
29090
  onToggleSelect: (path37) => setSelection((prev) => toggleSelection(prev, path37))
28315
29091
  })
28316
29092
  }),
28317
- /* @__PURE__ */ jsx14(Divider, {
29093
+ /* @__PURE__ */ jsx17(Divider, {
28318
29094
  splitRatio,
28319
29095
  totalWidth,
28320
29096
  onChange: setSplitRatio,
28321
29097
  onDraggingChange: setIsResizing
28322
29098
  }),
28323
- /* @__PURE__ */ jsx14("box", {
29099
+ /* @__PURE__ */ jsx17("box", {
28324
29100
  width: rightCols,
28325
29101
  height: "100%",
28326
29102
  flexDirection: "column",
28327
- children: /* @__PURE__ */ jsx14(DetailPane, {
28328
- selectedRow
29103
+ children: /* @__PURE__ */ jsx17(TabPane, {
29104
+ selectedRow,
29105
+ tabs: tabsForSelected,
29106
+ activeId: activeTabId,
29107
+ focused: terminalFocused && tabsForSelected.some((t) => t.id === activeTabId && t.id !== "details"),
29108
+ canAdd: sessionsForSelected.length < MAX_TERMINAL_SESSIONS,
29109
+ onSelect: handleSelectTab,
29110
+ onAdd: handleAddSession,
29111
+ onClose: handleCloseSession
28329
29112
  })
28330
29113
  })
28331
29114
  ]
28332
29115
  }),
28333
- isFiltering && /* @__PURE__ */ jsxs12("box", {
29116
+ isFiltering && /* @__PURE__ */ jsxs15("box", {
28334
29117
  flexDirection: "row",
28335
29118
  paddingX: 1,
28336
29119
  border: true,
28337
29120
  borderColor: "magenta",
28338
29121
  children: [
28339
- /* @__PURE__ */ jsx14("text", {
29122
+ /* @__PURE__ */ jsx17("text", {
28340
29123
  children: "filter: "
28341
29124
  }),
28342
- /* @__PURE__ */ jsx14("input", {
29125
+ /* @__PURE__ */ jsx17("input", {
28343
29126
  focused: true,
28344
29127
  placeholder: "Type to filter...",
28345
29128
  onInput: (v) => setFilterText(v)
28346
29129
  })
28347
29130
  ]
28348
29131
  }),
28349
- /* @__PURE__ */ jsx14(Footer, {
29132
+ /* @__PURE__ */ jsx17(Footer, {
28350
29133
  loading: loading || refreshing,
28351
29134
  lastRefreshed,
28352
29135
  errorCount: warnings.length,
@@ -28357,9 +29140,9 @@ function App({ opts }) {
28357
29140
  onHintClick: handleHintClick,
28358
29141
  onErrorClick: () => setModal({ type: "warnings" })
28359
29142
  }),
28360
- modal.type === "help" && /* @__PURE__ */ jsx14(HelpOverlay, {}),
28361
- modal.type === "history" && /* @__PURE__ */ jsx14(HistoryOverlay, {}),
28362
- modal.type === "warnings" && /* @__PURE__ */ jsx14(WarningsOverlay, {
29143
+ modal.type === "help" && /* @__PURE__ */ jsx17(HelpOverlay, {}),
29144
+ modal.type === "history" && /* @__PURE__ */ jsx17(HistoryOverlay, {}),
29145
+ modal.type === "warnings" && /* @__PURE__ */ jsx17(WarningsOverlay, {
28363
29146
  warnings,
28364
29147
  onAcknowledge: () => {
28365
29148
  clearWarnings();
@@ -28367,7 +29150,7 @@ function App({ opts }) {
28367
29150
  },
28368
29151
  onClose: () => setModal({ type: "none" })
28369
29152
  }),
28370
- modal.type === "error" && /* @__PURE__ */ jsx14(ConfirmModal, {
29153
+ modal.type === "error" && /* @__PURE__ */ jsx17(ConfirmModal, {
28371
29154
  title: "Error",
28372
29155
  message: modal.message,
28373
29156
  onConfirm: () => {
@@ -28385,7 +29168,7 @@ function App({ opts }) {
28385
29168
  }
28386
29169
  }
28387
29170
  }),
28388
- modal.type === "confirm_remove" && /* @__PURE__ */ jsx14(ConfirmModal, {
29171
+ modal.type === "confirm_remove" && /* @__PURE__ */ jsx17(ConfirmModal, {
28389
29172
  title: `Remove ${modal.rows.length} Worktree(s)`,
28390
29173
  message: (() => {
28391
29174
  const count2 = modal.rows.length;
@@ -28414,7 +29197,7 @@ function App({ opts }) {
28414
29197
  },
28415
29198
  onCancel: () => setModal({ type: "none" })
28416
29199
  }),
28417
- modal.type === "confirm_rebase" && /* @__PURE__ */ jsx14(ConfirmModal, {
29200
+ modal.type === "confirm_rebase" && /* @__PURE__ */ jsx17(ConfirmModal, {
28418
29201
  title: `Rebase ${modal.rows.length} Worktree(s)`,
28419
29202
  message: `Are you sure you want to fetch and rebase ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`,
28420
29203
  onConfirm: () => {
@@ -28424,7 +29207,7 @@ function App({ opts }) {
28424
29207
  },
28425
29208
  onCancel: () => setModal({ type: "none" })
28426
29209
  }),
28427
- modal.type === "confirm_sync" && /* @__PURE__ */ jsx14(ConfirmModal, {
29210
+ modal.type === "confirm_sync" && /* @__PURE__ */ jsx17(ConfirmModal, {
28428
29211
  title: `Sync ${modal.rows.length} Worktree(s)`,
28429
29212
  message: `Are you sure you want to sync ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`,
28430
29213
  onConfirm: () => {
@@ -28434,7 +29217,7 @@ function App({ opts }) {
28434
29217
  },
28435
29218
  onCancel: () => setModal({ type: "none" })
28436
29219
  }),
28437
- createModal && /* @__PURE__ */ jsx14(InputModal, {
29220
+ createModal && /* @__PURE__ */ jsx17(InputModal, {
28438
29221
  title: `New worktree branch in ${selectedRow?.repoName ?? ""}`,
28439
29222
  placeholder: "Branch name (empty to cancel)",
28440
29223
  errorMessage: createError,
@@ -28461,7 +29244,7 @@ function App({ opts }) {
28461
29244
  setCreateBaseModal({ branch, repoName });
28462
29245
  }
28463
29246
  }),
28464
- createBaseModal && /* @__PURE__ */ jsx14(InputModal, {
29247
+ createBaseModal && /* @__PURE__ */ jsx17(InputModal, {
28465
29248
  title: `Base ref for ${createBaseModal.branch}`,
28466
29249
  placeholder: "origin/main (empty for default main)",
28467
29250
  errorMessage: createBaseError,
@@ -28477,7 +29260,7 @@ function App({ opts }) {
28477
29260
  setCreateDepsChoice({ branch, repoName, base: base2 || undefined });
28478
29261
  }
28479
29262
  }),
28480
- createDepsChoice && /* @__PURE__ */ jsx14(ChoiceModal, {
29263
+ createDepsChoice && /* @__PURE__ */ jsx17(ChoiceModal, {
28481
29264
  title: `Dependencies for ${createDepsChoice.branch}`,
28482
29265
  options: DEPS_CHOICES,
28483
29266
  onSubmit: (choice) => {
@@ -28487,7 +29270,7 @@ function App({ opts }) {
28487
29270
  },
28488
29271
  onCancel: () => setCreateDepsChoice(null)
28489
29272
  }),
28490
- pullPrModal && /* @__PURE__ */ jsx14(InputModal, {
29273
+ pullPrModal && /* @__PURE__ */ jsx17(InputModal, {
28491
29274
  title: `Pull PR into ${selectedRow?.repoName ?? ""}`,
28492
29275
  placeholder: "https://github.com/owner/repo/pull/123",
28493
29276
  errorMessage: pullPrError,
@@ -28515,7 +29298,7 @@ function App({ opts }) {
28515
29298
  setPullForceChoice({ link, repoName });
28516
29299
  }
28517
29300
  }),
28518
- pullForceChoice && /* @__PURE__ */ jsx14(ChoiceModal, {
29301
+ pullForceChoice && /* @__PURE__ */ jsx17(ChoiceModal, {
28519
29302
  title: `Pull ${pullForceChoice.link.split("/").pop()}?`,
28520
29303
  options: [
28521
29304
  { value: "normal", label: "Pull (skip if exists)", desc: "Fails if branch already exists" },
@@ -28528,7 +29311,7 @@ function App({ opts }) {
28528
29311
  },
28529
29312
  onCancel: () => setPullForceChoice(null)
28530
29313
  }),
28531
- renameModal && selectedRow && /* @__PURE__ */ jsx14(InputModal, {
29314
+ renameModal && selectedRow && /* @__PURE__ */ jsx17(InputModal, {
28532
29315
  title: `Rename branch ${selectedRow.branch}`,
28533
29316
  initialValue: selectedRow.branch,
28534
29317
  placeholder: "New branch name",
@@ -28553,7 +29336,7 @@ function App({ opts }) {
28553
29336
  setModal({ type: "confirm_rename", row: target, to });
28554
29337
  }
28555
29338
  }),
28556
- modal.type === "confirm_rename" && /* @__PURE__ */ jsx14(ConfirmModal, {
29339
+ modal.type === "confirm_rename" && /* @__PURE__ */ jsx17(ConfirmModal, {
28557
29340
  title: "Rename Worktree",
28558
29341
  message: [
28559
29342
  `${modal.row.branch} → ${modal.to}`,
@@ -28584,12 +29367,12 @@ function App({ opts }) {
28584
29367
  },
28585
29368
  onCancel: () => setModal({ type: "none" })
28586
29369
  }),
28587
- configOpen && /* @__PURE__ */ jsx14(ConfigOverlay, {
29370
+ configOpen && /* @__PURE__ */ jsx17(ConfigOverlay, {
28588
29371
  onClose: () => setConfigOpen(false),
28589
29372
  onSaved: () => void doRefresh(),
28590
29373
  onError: (msg) => setModal({ type: "error", message: msg })
28591
29374
  }),
28592
- failedLogs.length > 0 && /* @__PURE__ */ jsx14(ActionLogModal, {
29375
+ failedLogs.length > 0 && /* @__PURE__ */ jsx17(ActionLogModal, {
28593
29376
  title: failedLogs[0].title,
28594
29377
  lines: failedLogs[0].lines,
28595
29378
  done: true,
@@ -28603,7 +29386,9 @@ var VERBS, capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1), DEPS_CHOI
28603
29386
  var init_App = __esm(() => {
28604
29387
  init_useWorktrees();
28605
29388
  init_WorktreeTable();
28606
- init_DetailPane();
29389
+ init_TabPane();
29390
+ init_DetailsTab();
29391
+ init_TerminalView();
28607
29392
  init_Footer();
28608
29393
  init_Divider();
28609
29394
  init_HelpOverlay();
@@ -28616,10 +29401,9 @@ var init_App = __esm(() => {
28616
29401
  init_ConfigOverlay();
28617
29402
  init_WarningsOverlay();
28618
29403
  init_utils();
28619
- init_agents();
28620
29404
  init_config();
28621
- init_history();
28622
29405
  init_useSpinnerFrame();
29406
+ init_useTerminalSessions();
28623
29407
  VERBS = {
28624
29408
  create: "creating worktree",
28625
29409
  remove: "deleting",
@@ -28643,16 +29427,16 @@ var exports_tui = {};
28643
29427
  __export(exports_tui, {
28644
29428
  runTerminal: () => runTerminal
28645
29429
  });
28646
- import { createCliRenderer, TextTableRenderable } from "@opentui/core";
29430
+ import { createCliRenderer, TextTableRenderable, EmbeddedTerminalRenderable } from "@opentui/core";
28647
29431
  import { createRoot, extend as extend2 } from "@opentui/react";
28648
- import { jsx as jsx15 } from "@opentui/react/jsx-runtime";
29432
+ import { jsx as jsx18 } from "@opentui/react/jsx-runtime";
28649
29433
  async function runTerminal(opts) {
28650
29434
  const renderer = await createCliRenderer({
28651
29435
  exitOnCtrlC: false
28652
29436
  });
28653
29437
  const root = createRoot(renderer);
28654
29438
  try {
28655
- root.render(/* @__PURE__ */ jsx15(App, {
29439
+ root.render(/* @__PURE__ */ jsx18(App, {
28656
29440
  opts
28657
29441
  }));
28658
29442
  } catch (err) {
@@ -28663,7 +29447,7 @@ async function runTerminal(opts) {
28663
29447
  }
28664
29448
  var init_tui = __esm(() => {
28665
29449
  init_App();
28666
- extend2({ "text-table": TextTableRenderable });
29450
+ extend2({ "text-table": TextTableRenderable, "embedded-terminal": EmbeddedTerminalRenderable });
28667
29451
  });
28668
29452
 
28669
29453
  // node_modules/commander/lib/error.js
@@ -31002,7 +31786,80 @@ async function runPostCreateSetup(params) {
31002
31786
  init_deps();
31003
31787
  init_forge();
31004
31788
  init_owner();
31005
- init_agents();
31789
+
31790
+ // src/lib/agents.ts
31791
+ init_execa();
31792
+ var DEFAULT_AGENTS = {
31793
+ claude: "claude",
31794
+ codex: "codex",
31795
+ opencode: "opencode",
31796
+ cursor: "cursor"
31797
+ };
31798
+ function resolveAgentCommand(name, configured) {
31799
+ return configured?.[name]?.command ?? DEFAULT_AGENTS[name] ?? null;
31800
+ }
31801
+ function listAvailableAgents(configured) {
31802
+ return [...new Set([...Object.keys(DEFAULT_AGENTS), ...Object.keys(configured ?? {})])];
31803
+ }
31804
+ var tmuxAvailable = undefined;
31805
+ async function isTmuxAvailable() {
31806
+ if (tmuxAvailable !== undefined)
31807
+ return tmuxAvailable;
31808
+ try {
31809
+ await execa("tmux", ["-V"]);
31810
+ tmuxAvailable = true;
31811
+ } catch {
31812
+ tmuxAvailable = false;
31813
+ }
31814
+ return tmuxAvailable;
31815
+ }
31816
+ function tmuxSessionName(repoName, branch) {
31817
+ const sanitized = `${repoName}-${branch}`.replace(/[\/.:]/g, "-");
31818
+ return `wtx-${sanitized}`.substring(0, 60);
31819
+ }
31820
+ function buildTmuxArgs(session, wtPath, cmd) {
31821
+ return ["new-session", "-d", "-s", session, "-c", wtPath, cmd];
31822
+ }
31823
+ async function spawnAgentInWorktree(commandTemplate, wtPath, opts = {}) {
31824
+ const cmd = buildAgentCommand(commandTemplate, wtPath, opts.prompt, {
31825
+ branch: opts.branch,
31826
+ repo: opts.repoName
31827
+ });
31828
+ const session = opts.repoName && opts.branch ? tmuxSessionName(opts.repoName, opts.branch) : undefined;
31829
+ if (opts.dryRun) {
31830
+ if (session && await isTmuxAvailable()) {
31831
+ return { mode: "tmux", session };
31832
+ }
31833
+ return { mode: "direct" };
31834
+ }
31835
+ if (session && await isTmuxAvailable()) {
31836
+ try {
31837
+ const args = buildTmuxArgs(session, wtPath, cmd);
31838
+ await execa("tmux", args, { stdio: "inherit" });
31839
+ return { mode: "tmux", session };
31840
+ } catch {}
31841
+ }
31842
+ await execa(cmd, { shell: true, cwd: wtPath, stdio: "inherit" });
31843
+ return { mode: "direct" };
31844
+ }
31845
+ function shellQuote(value) {
31846
+ return `'${value.replaceAll("'", `'''`)}'`;
31847
+ }
31848
+ function buildAgentCommand(commandTemplate, wtPath, prompt, vars) {
31849
+ let cmd = commandTemplate.replaceAll("{wt}", shellQuote(wtPath));
31850
+ if (vars?.branch) {
31851
+ cmd = cmd.replaceAll("{branch}", shellQuote(vars.branch));
31852
+ }
31853
+ if (vars?.repo) {
31854
+ cmd = cmd.replaceAll("{repo}", shellQuote(vars.repo));
31855
+ }
31856
+ if (prompt) {
31857
+ cmd += ` ${shellQuote(prompt)}`;
31858
+ }
31859
+ return cmd;
31860
+ }
31861
+
31862
+ // src/commands/create.ts
31006
31863
  var DEPS_STRATEGIES = ["auto", "link", "symlink", "install", "off"];
31007
31864
  async function applyDepsStrategy(strategy, wtPath, mainPath, opts) {
31008
31865
  if (strategy === "install") {