abmux 0.0.7 → 0.0.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 (3) hide show
  1. package/README.md +100 -24
  2. package/dist/cli/index.js +315 -182
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,14 +1,8 @@
1
1
  # abmux
2
2
 
3
- AI Board on tmux — A terminal UI for managing Claude Code sessions on tmux.
3
+ AI Board on tmux — A TUI for managing multiple Claude Code sessions from a single terminal screen.
4
4
 
5
- ## Features
6
-
7
- - Manage tmux sessions grouped by project
8
- - Detect Claude session status: waiting for input, thinking, running tools, idle
9
- - Launch new Claude Code sessions with prompts via your `$EDITOR`
10
- - Fuzzy search directories to add new sessions
11
- - CLI commands for scripting: `new`, `open`, `kill`, `list`
5
+ Monitor, create, switch between, and delete Claude Code sessions running on tmux, all in one place.
12
6
 
13
7
  ## Requirements
14
8
 
@@ -19,34 +13,116 @@ AI Board on tmux — A terminal UI for managing Claude Code sessions on tmux.
19
13
  ## Install
20
14
 
21
15
  ```sh
22
- npm install -g abmux
23
- # or
24
- yarn global add abmux
25
- # or
26
16
  pnpm add -g abmux
27
17
  ```
28
18
 
29
- ## Usage
19
+ ## Getting Started
30
20
 
31
21
  ```sh
32
- abmux # Start TUI
33
- abmux new <prompt> [--dir <path>] # Create session and launch Claude
34
- abmux open [session] # Attach to session
35
- abmux kill [session] # Kill session
22
+ abmux
23
+ ```
24
+
25
+ Run without arguments to launch the TUI.
26
+
27
+ CLI commands are also available for scripting:
28
+
29
+ ```sh
30
+ abmux new <prompt> [--dir <path>] # Create a session
31
+ abmux open [session] # Attach to a session
32
+ abmux kill [session] # Kill a session
36
33
  abmux list # List sessions
37
- abmux --help # Show help
38
34
  ```
39
35
 
36
+ ## Screen Layout
37
+
38
+ The main screen is split into three panels:
39
+
40
+ ```
41
+ ┌─────────────────────────────────────────────────────────────────┐
42
+ │ abmux - v0.0.x │
43
+ ├───────────────────────┬─────────────────────────────────────────┤
44
+ │ │ │
45
+ │ Session List │ Pane List │
46
+ │ (Left Panel) │ (Right Panel) │
47
+ │ │ │
48
+ │ > my-project (cwd) │ ⠋ [thinking] Refactoring... %5 │
49
+ │ other-project │ ✳ [running] Fixing tests %8 │
50
+ │ │ ○ [idle] Waiting %12 │
51
+ │ │ ● vim %3 │
52
+ │ │ │
53
+ ├───────────────────────┴─────────────────────────────────────────┤
54
+ │ │
55
+ │ Session Overview (Bottom Panel) │
56
+ │ Summaries of what Claude is working on in each session │
57
+ │ │
58
+ ├──────────────────────────────────────────────────────────────────┤
59
+ │ ↑/↓ move Enter select Tab next n add q quit ● 2 thinking │
60
+ └──────────────────────────────────────────────────────────────────┘
61
+ ```
62
+
63
+ Press `Tab` to cycle focus: Left → Right → Bottom. The focused panel is highlighted with a green border.
64
+
65
+ ## Panels
66
+
67
+ ### Left Panel: Session List
68
+
69
+ Lists tmux sessions grouped by project directory. The session matching your current directory is marked with `(cwd)`.
70
+
71
+ | Key | Action |
72
+ | ------------- | ----------------------------------- |
73
+ | `↑` / `↓` | Move cursor |
74
+ | `Enter` / `→` | Select session, move to right panel |
75
+ | `n` | Add session via directory search |
76
+ | `d` | Delete session |
77
+ | `q` | Quit |
78
+
79
+ ### Right Panel: Pane List
80
+
81
+ Shows all panes in the selected session. Claude Code panes display their status; other panes (editors, shells) are also listed.
82
+
83
+ | Key | Action |
84
+ | ----------- | ------------------------------- |
85
+ | `↑` / `↓` | Move cursor |
86
+ | `Enter` | Attach to pane (switch to tmux) |
87
+ | `n` | Create a new Claude session |
88
+ | `v` | Open session in `$EDITOR` |
89
+ | `d` | Kill pane |
90
+ | `Esc` / `←` | Back to left panel |
91
+
92
+ ### Bottom Panel: Session Overview
93
+
94
+ Displays AI-generated summaries of what Claude is doing in each session. Auto-refreshes every 60 seconds.
95
+
96
+ | Key | Action |
97
+ | ----------- | ------------------ |
98
+ | `↑` / `↓` | Scroll |
99
+ | `Tab` | Next panel |
100
+ | `Esc` / `←` | Back to left panel |
101
+
102
+ ## Status Icons
103
+
104
+ The right panel and the status bar show Claude session states with these icons:
105
+
106
+ | Icon | Status | Meaning |
107
+ | ------------- | -------- | ------------------------- |
108
+ | `⠋` (braille) | thinking | Claude is reasoning |
109
+ | `✳` | running | Executing a tool |
110
+ | `❓` | confirm | Waiting for user approval |
111
+ | `❯` | waiting | Ready for input |
112
+ | `○` | idle | Idle |
113
+
114
+ Non-Claude panes show `●` (busy) or `○` (available).
115
+
40
116
  ## Development
41
117
 
42
118
  ```sh
43
119
  pnpm install
44
- pnpm start # Run in development mode (tsx)
45
- pnpm test # Run tests with Vitest
46
- pnpm typecheck # Type check without emitting
47
- pnpm lint:check # Lint with oxlint
48
- pnpm format:check # Check formatting with oxfmt
49
- pnpm build # Bundle with esbuild
120
+ pnpm start # Run in dev mode
121
+ pnpm test # Run tests
122
+ pnpm typecheck # Type check
123
+ pnpm lint:check # Lint
124
+ pnpm format:check # Format check
125
+ pnpm build # Bundle
50
126
  ```
51
127
 
52
128
  ## License
package/dist/cli/index.js CHANGED
@@ -532,7 +532,12 @@ var createManagerUsecase = (context) => {
532
532
  const { tmux, sessionDetection } = context.services;
533
533
  const windowTarget = (up) => `${up.pane.sessionName}:${String(up.pane.windowIndex)}`;
534
534
  return {
535
- createSession: async ({ sessionName, cwd, prompt }) => {
535
+ createSession: async ({
536
+ sessionName,
537
+ cwd,
538
+ prompt,
539
+ worktree
540
+ }) => {
536
541
  const exists2 = await context.infra.tmuxCli.hasSession(sessionName);
537
542
  if (!exists2) {
538
543
  await context.infra.tmuxCli.newSession({ name: sessionName, cwd });
@@ -544,7 +549,8 @@ var createManagerUsecase = (context) => {
544
549
  const target = sessionPanes[0]?.paneId;
545
550
  if (!target) return;
546
551
  const escapedPrompt = escapeShellArg(prompt);
547
- await tmux.sendCommand({ target, command: `claude -w -- ${escapedPrompt}` });
552
+ const worktreeFlag = worktree ? " -w" : "";
553
+ await tmux.sendCommand({ target, command: `claude${worktreeFlag} -- ${escapedPrompt}` });
548
554
  },
549
555
  list: async () => {
550
556
  const panes = await tmux.listPanes();
@@ -597,7 +603,7 @@ var createUsecases = (context) => ({
597
603
  // package.json
598
604
  var package_default = {
599
605
  name: "abmux",
600
- version: "0.0.7",
606
+ version: "0.0.9",
601
607
  repository: {
602
608
  type: "git",
603
609
  url: "https://github.com/cut0/abmux.git"
@@ -667,8 +673,8 @@ import { createElement } from "react";
667
673
 
668
674
  // src/components/ManagerView.tsx
669
675
  import { basename as basename2 } from "node:path";
670
- import { Box as Box11 } from "ink";
671
- import { useCallback as useCallback4, useMemo as useMemo7, useRef as useRef3, useState as useState6 } from "react";
676
+ import { Box as Box12, useInput as useInput8 } from "ink";
677
+ import { useCallback as useCallback5, useMemo as useMemo7, useRef as useRef4, useState as useState8 } from "react";
672
678
 
673
679
  // src/components/shared/Header.tsx
674
680
  import { Box, Text } from "ink";
@@ -769,10 +775,11 @@ var SessionListPanel = ({
769
775
  onSelect,
770
776
  onCursorChange,
771
777
  onDeleteSession,
772
- onAddSession
778
+ onAddSession,
779
+ cursorRef
773
780
  }) => {
774
781
  const { exit } = useApp();
775
- const [cursor, setCursor] = useState(0);
782
+ const [cursor, setCursor] = useState(cursorRef?.current ?? 0);
776
783
  const sortedSessions = useMemo3(
777
784
  () => sortSessions(sessions, currentSession),
778
785
  [sessions, currentSession]
@@ -782,6 +789,7 @@ var SessionListPanel = ({
782
789
  if (clampedCursor !== cursor) {
783
790
  setCursor(clampedCursor);
784
791
  }
792
+ if (cursorRef) cursorRef.current = clampedCursor;
785
793
  const reservedLines = 1;
786
794
  const { scrollOffset, visibleCount } = useScroll(
787
795
  clampedCursor,
@@ -855,6 +863,7 @@ var SessionListPanel = ({
855
863
 
856
864
  // src/components/PaneListPanel.tsx
857
865
  import { Box as Box6, Text as Text6 } from "ink";
866
+ import { useRef as useRef2 } from "react";
858
867
 
859
868
  // src/components/PaneListView.tsx
860
869
  import { Box as Box5, Text as Text5, useApp as useApp2, useInput as useInput2 } from "ink";
@@ -902,16 +911,19 @@ var PaneListView = ({
902
911
  onUnhighlight,
903
912
  onBack,
904
913
  onNewSession,
905
- onKillPane
914
+ onOpenEditor,
915
+ onKillPane,
916
+ cursorRef
906
917
  }) => {
907
918
  const { exit } = useApp2();
908
- const [cursor, setCursor] = useState2(0);
919
+ const [cursor, setCursor] = useState2(cursorRef?.current ?? 0);
909
920
  const highlightedRef = useRef(void 0);
910
921
  const panes = useMemo4(() => group.tabs.flatMap((t) => t.panes), [group]);
911
922
  const clampedCursor = cursor >= panes.length ? Math.max(0, panes.length - 1) : cursor;
912
923
  if (clampedCursor !== cursor) {
913
924
  setCursor(clampedCursor);
914
925
  }
926
+ if (cursorRef) cursorRef.current = clampedCursor;
915
927
  const reservedLines = 1;
916
928
  const { scrollOffset, visibleCount } = useScroll(
917
929
  clampedCursor,
@@ -980,6 +992,10 @@ var PaneListView = ({
980
992
  onNewSession(selectedSession);
981
993
  return;
982
994
  }
995
+ if (input === "v") {
996
+ onOpenEditor(selectedSession);
997
+ return;
998
+ }
983
999
  if (key.return) {
984
1000
  const pane = panes[clampedCursor];
985
1001
  if (pane) onNavigate(pane);
@@ -1000,7 +1016,7 @@ var PaneListView = ({
1000
1016
  ")"
1001
1017
  ] })
1002
1018
  ] }),
1003
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: panes.length === 0 ? /* @__PURE__ */ jsx5(Box5, { paddingLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No panes. Press n to create." }) }) : visiblePanes.map((up, i) => /* @__PURE__ */ jsx5(
1019
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: panes.length === 0 ? /* @__PURE__ */ jsx5(Box5, { paddingLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No panes. Press n or v to create." }) }) : visiblePanes.map((up, i) => /* @__PURE__ */ jsx5(
1004
1020
  PaneItem,
1005
1021
  {
1006
1022
  unifiedPane: up,
@@ -1023,8 +1039,15 @@ var PaneListPanel = ({
1023
1039
  onUnhighlight,
1024
1040
  onBack,
1025
1041
  onNewSession,
1026
- onKillPane
1042
+ onOpenEditor,
1043
+ onKillPane,
1044
+ cursorRef
1027
1045
  }) => {
1046
+ const prevSessionRef = useRef2(selectedSession);
1047
+ if (prevSessionRef.current !== selectedSession) {
1048
+ prevSessionRef.current = selectedSession;
1049
+ if (cursorRef) cursorRef.current = 0;
1050
+ }
1028
1051
  if (!selectedSession) {
1029
1052
  return /* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No session selected" }) });
1030
1053
  }
@@ -1040,7 +1063,9 @@ var PaneListPanel = ({
1040
1063
  onUnhighlight,
1041
1064
  onBack,
1042
1065
  onNewSession,
1043
- onKillPane
1066
+ onOpenEditor,
1067
+ onKillPane,
1068
+ cursorRef
1044
1069
  },
1045
1070
  selectedSession
1046
1071
  );
@@ -1058,10 +1083,11 @@ var SessionOverviewPanel = ({
1058
1083
  isLoading,
1059
1084
  isFocused,
1060
1085
  availableRows,
1061
- onBack
1086
+ onBack,
1087
+ cursorRef
1062
1088
  }) => {
1063
1089
  const { exit } = useApp3();
1064
- const [cursor, setCursor] = useState3(0);
1090
+ const [cursor, setCursor] = useState3(cursorRef?.current ?? 0);
1065
1091
  const lines = useMemo5(() => {
1066
1092
  const summaryLines = overallSummary ? [
1067
1093
  { key: "summary", type: "summary", text: overallSummary },
@@ -1094,6 +1120,7 @@ var SessionOverviewPanel = ({
1094
1120
  return [...summaryLines, ...sessionLines];
1095
1121
  }, [overallSummary, items, groups]);
1096
1122
  const clampedCursor = cursor >= lines.length ? Math.max(0, lines.length - 1) : cursor;
1123
+ if (cursorRef) cursorRef.current = clampedCursor;
1097
1124
  const reservedLines = 3;
1098
1125
  const { scrollOffset, visibleCount } = useScroll(
1099
1126
  clampedCursor,
@@ -1170,6 +1197,7 @@ var SessionOverviewPanel = ({
1170
1197
 
1171
1198
  // src/components/ConfirmView.tsx
1172
1199
  import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
1200
+ import { useCallback as useCallback4, useState as useState5 } from "react";
1173
1201
 
1174
1202
  // src/hooks/use-terminal-size.ts
1175
1203
  import { useStdout } from "ink";
@@ -1199,20 +1227,35 @@ var useTerminalSize = () => {
1199
1227
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1200
1228
  var ConfirmView = ({ selectedDir, prompt, onConfirm, onCancel }) => {
1201
1229
  const { rows } = useTerminalSize();
1230
+ const [worktree, setWorktree] = useState5(true);
1202
1231
  const previewLines = prompt.split("\n");
1203
- const maxPreview = Math.min(previewLines.length, rows - 6);
1232
+ const maxPreview = Math.min(previewLines.length, rows - 7);
1233
+ const handleToggleWorktree = useCallback4(() => {
1234
+ setWorktree((prev) => !prev);
1235
+ }, []);
1204
1236
  useInput4((_input, key) => {
1205
1237
  if (key.return) {
1206
- onConfirm();
1238
+ onConfirm({ worktree });
1207
1239
  return;
1208
1240
  }
1209
1241
  if (key.escape) {
1210
1242
  onCancel();
1243
+ return;
1244
+ }
1245
+ if (key.tab) {
1246
+ handleToggleWorktree();
1211
1247
  }
1212
1248
  });
1213
1249
  return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", height: rows, children: [
1214
1250
  /* @__PURE__ */ jsx8(Header, { title: `${APP_TITLE} \u2014 ${selectedDir}` }),
1215
- /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { bold: true, children: "New Claude session:" }) }),
1251
+ /* @__PURE__ */ jsxs6(Box8, { marginBottom: 1, gap: 2, children: [
1252
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "New Claude session:" }),
1253
+ /* @__PURE__ */ jsxs6(Text8, { color: worktree ? "green" : "gray", children: [
1254
+ "worktree: [",
1255
+ worktree ? "ON" : "OFF",
1256
+ "]"
1257
+ ] })
1258
+ ] }),
1216
1259
  /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingLeft: 2, children: [
1217
1260
  previewLines.slice(0, maxPreview).map((line, i) => /* @__PURE__ */ jsx8(Text8, { color: "white", children: line }, i)),
1218
1261
  previewLines.length > maxPreview && /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
@@ -1223,6 +1266,7 @@ var ConfirmView = ({ selectedDir, prompt, onConfirm, onCancel }) => {
1223
1266
  ] }),
1224
1267
  /* @__PURE__ */ jsxs6(Box8, { gap: 2, children: [
1225
1268
  /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Enter confirm" }),
1269
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Tab worktree" }),
1226
1270
  /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Esc cancel" })
1227
1271
  ] })
1228
1272
  ] });
@@ -1266,7 +1310,7 @@ var DeleteSessionView = ({ sessionName, paneCount, onConfirm, onCancel }) => {
1266
1310
 
1267
1311
  // src/components/DirectorySearchView.tsx
1268
1312
  import { Box as Box10, Text as Text10, useInput as useInput6 } from "ink";
1269
- import { useMemo as useMemo6, useState as useState5 } from "react";
1313
+ import { useMemo as useMemo6, useState as useState6 } from "react";
1270
1314
  import { basename } from "node:path";
1271
1315
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1272
1316
  var formatPath = (path) => {
@@ -1278,8 +1322,8 @@ var formatPath = (path) => {
1278
1322
  };
1279
1323
  var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
1280
1324
  const { rows } = useTerminalSize();
1281
- const [query, setQuery] = useState5("");
1282
- const [cursor, setCursor] = useState5(0);
1325
+ const [query, setQuery] = useState6("");
1326
+ const [cursor, setCursor] = useState6(0);
1283
1327
  const filtered = useMemo6(() => {
1284
1328
  if (!query) return directories;
1285
1329
  const lower = query.toLowerCase();
@@ -1354,10 +1398,53 @@ var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
1354
1398
  ] });
1355
1399
  };
1356
1400
 
1401
+ // src/components/PromptInputView.tsx
1402
+ import { Box as Box11, Text as Text11, useInput as useInput7 } from "ink";
1403
+ import { useState as useState7 } from "react";
1404
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1405
+ var PromptInputView = ({ selectedDir, onSubmit, onCancel }) => {
1406
+ const { rows } = useTerminalSize();
1407
+ const [value, setValue] = useState7("");
1408
+ useInput7((input, key) => {
1409
+ if (key.escape) {
1410
+ onCancel();
1411
+ return;
1412
+ }
1413
+ if (key.return) {
1414
+ const trimmed = value.trim();
1415
+ if (trimmed) onSubmit(trimmed);
1416
+ return;
1417
+ }
1418
+ if (key.backspace || key.delete) {
1419
+ setValue((prev) => prev.slice(0, -1));
1420
+ return;
1421
+ }
1422
+ if (input && !key.ctrl && !key.meta) {
1423
+ setValue((prev) => prev + input);
1424
+ }
1425
+ });
1426
+ return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: rows, children: [
1427
+ /* @__PURE__ */ jsx11(Header, { title: `${APP_TITLE} \u2014 ${selectedDir}` }),
1428
+ /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { bold: true, children: "New prompt:" }) }),
1429
+ /* @__PURE__ */ jsxs9(Box11, { paddingLeft: 1, flexGrow: 1, children: [
1430
+ /* @__PURE__ */ jsxs9(Text11, { color: "green", children: [
1431
+ ">",
1432
+ " "
1433
+ ] }),
1434
+ /* @__PURE__ */ jsx11(Text11, { children: value }),
1435
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: value ? "" : "type your prompt..." })
1436
+ ] }),
1437
+ /* @__PURE__ */ jsxs9(Box11, { gap: 2, children: [
1438
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Enter submit" }),
1439
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Esc cancel" })
1440
+ ] })
1441
+ ] });
1442
+ };
1443
+
1357
1444
  // src/hooks/use-interval.ts
1358
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
1445
+ import { useEffect as useEffect2, useRef as useRef3 } from "react";
1359
1446
  var useInterval = (fn, intervalMs, enabled = true) => {
1360
- const fnRef = useRef2(fn);
1447
+ const fnRef = useRef3(fn);
1361
1448
  fnRef.current = fn;
1362
1449
  useEffect2(() => {
1363
1450
  if (!enabled) return;
@@ -1380,13 +1467,7 @@ var swallow = async (fn) => {
1380
1467
  };
1381
1468
 
1382
1469
  // src/components/ManagerView.tsx
1383
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1384
- var MODE = {
1385
- split: "split",
1386
- confirm: "confirm",
1387
- deleteSession: "deleteSession",
1388
- addSession: "addSession"
1389
- };
1470
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1390
1471
  var FOCUS = {
1391
1472
  left: "left",
1392
1473
  right: "right",
@@ -1394,65 +1475,91 @@ var FOCUS = {
1394
1475
  };
1395
1476
  var POLL_INTERVAL = 3e3;
1396
1477
  var OVERVIEW_POLL_INTERVAL = 6e4;
1478
+ var FIXED_ROWS = 3;
1479
+ var TOP_HEIGHT_RATIO = 1 / 2;
1480
+ var LEFT_WIDTH_RATIO = 1 / 3;
1481
+ var initState = (remountState) => {
1482
+ const snapshot = remountState?.snapshot;
1483
+ return {
1484
+ view: remountState?.prompt ? { mode: "confirm", prompt: remountState.prompt, cwd: remountState.cwd ?? "" } : { mode: "split" },
1485
+ focus: snapshot?.focus ?? FOCUS.left,
1486
+ selectedSession: remountState?.session ?? snapshot?.selectedSession,
1487
+ sessions: [],
1488
+ sessionsLoading: true,
1489
+ overviewResult: snapshot?.overviewResult
1490
+ };
1491
+ };
1397
1492
  var ManagerView = ({
1398
1493
  actions,
1399
1494
  currentSession,
1400
1495
  directories,
1401
- restoredPrompt,
1402
- restoredSession,
1403
- restoredCwd
1496
+ remountState,
1497
+ snapshotRef
1404
1498
  }) => {
1405
1499
  const { rows, columns } = useTerminalSize();
1406
- const [sessionsState, setSessionsState] = useState6({
1407
- sessions: [],
1408
- isLoading: true
1500
+ const [state, setState] = useState8(() => initState(remountState));
1501
+ const overviewInFlightRef = useRef4(false);
1502
+ const snapshot = remountState?.snapshot;
1503
+ const cursorsRef = useRef4({
1504
+ session: { current: snapshot?.sessionListCursor ?? 0 },
1505
+ pane: { current: snapshot?.paneListCursor ?? 0 },
1506
+ overview: { current: snapshot?.overviewCursor ?? 0 }
1409
1507
  });
1410
- const [mode, setMode] = useState6(restoredPrompt ? MODE.confirm : MODE.split);
1411
- const [focus, setFocus] = useState6(FOCUS.left);
1412
- const [selectedSession, setSelectedSession] = useState6(restoredSession);
1413
- const [pendingPrompt, setPendingPrompt] = useState6(restoredPrompt ?? "");
1414
- const [pendingDeleteSession, setPendingDeleteSession] = useState6(void 0);
1415
- const [overviewResult, setOverviewResult] = useState6({
1416
- overallSummary: "",
1417
- sessions: []
1418
- });
1419
- const [overviewLoading, setOverviewLoading] = useState6(true);
1420
- const overviewInFlightRef = useRef3(false);
1421
- const refresh = useCallback4(async () => {
1508
+ const patch = useCallback5((partial) => {
1509
+ setState((prev) => ({ ...prev, ...partial }));
1510
+ }, []);
1511
+ const refresh = useCallback5(async () => {
1422
1512
  try {
1423
1513
  const fetched = await actions.fetchSessions();
1424
- setSessionsState((prev) => {
1514
+ setState((prev) => {
1425
1515
  const fetchedNames = new Set(fetched.map((s) => s.name));
1426
1516
  const userOnly = prev.sessions.filter(
1427
1517
  (s) => !fetchedNames.has(s.name) && s.groups.length === 0
1428
1518
  );
1429
- return { sessions: [...userOnly, ...fetched], isLoading: false };
1519
+ return { ...prev, sessions: [...userOnly, ...fetched], sessionsLoading: false };
1430
1520
  });
1431
1521
  } catch {
1432
- setSessionsState((prev) => ({ ...prev, isLoading: false }));
1522
+ patch({ sessionsLoading: false });
1433
1523
  }
1434
- }, [actions]);
1524
+ }, [actions, patch]);
1435
1525
  useInterval(() => void refresh(), POLL_INTERVAL);
1436
1526
  useInterval(
1437
1527
  () => {
1438
1528
  if (overviewInFlightRef.current) return;
1439
1529
  overviewInFlightRef.current = true;
1440
- setOverviewLoading(true);
1441
- void actions.fetchOverview(sessionsState.sessions).then((result) => {
1442
- setOverviewResult(result);
1530
+ void actions.fetchOverview(state.sessions).then((result) => {
1531
+ patch({ overviewResult: result });
1443
1532
  }).catch(() => {
1444
1533
  }).finally(() => {
1445
- setOverviewLoading(false);
1446
1534
  overviewInFlightRef.current = false;
1447
1535
  });
1448
1536
  },
1449
1537
  OVERVIEW_POLL_INTERVAL,
1450
- !sessionsState.isLoading
1538
+ !state.sessionsLoading
1451
1539
  );
1452
- const resolvedSession = selectedSession ?? sessionsState.sessions[0]?.name;
1540
+ useInput8(
1541
+ (_input, key) => {
1542
+ if (key.tab) {
1543
+ setState((prev) => {
1544
+ const next = prev.focus === FOCUS.left ? FOCUS.right : prev.focus === FOCUS.right ? FOCUS.bottom : FOCUS.left;
1545
+ return { ...prev, focus: next };
1546
+ });
1547
+ }
1548
+ },
1549
+ { isActive: state.view.mode === "split" }
1550
+ );
1551
+ const resolvedSession = state.selectedSession ?? state.sessions[0]?.name;
1552
+ snapshotRef.current = {
1553
+ focus: state.focus,
1554
+ selectedSession: resolvedSession,
1555
+ sessionListCursor: cursorsRef.current.session.current,
1556
+ paneListCursor: cursorsRef.current.pane.current,
1557
+ overviewCursor: cursorsRef.current.overview.current,
1558
+ overviewResult: state.overviewResult ?? { overallSummary: "", sessions: [] }
1559
+ };
1453
1560
  const selectedManagedSession = useMemo7(
1454
- () => sessionsState.sessions.find((s) => s.name === resolvedSession),
1455
- [sessionsState.sessions, resolvedSession]
1561
+ () => state.sessions.find((s) => s.name === resolvedSession),
1562
+ [state.sessions, resolvedSession]
1456
1563
  );
1457
1564
  const selectedGroup = useMemo7(() => {
1458
1565
  if (!selectedManagedSession) return void 0;
@@ -1461,10 +1568,7 @@ var ManagerView = ({
1461
1568
  tabs: selectedManagedSession.groups.flatMap((g) => g.tabs)
1462
1569
  };
1463
1570
  }, [selectedManagedSession]);
1464
- const allGroups = useMemo7(
1465
- () => sessionsState.sessions.flatMap((s) => s.groups),
1466
- [sessionsState.sessions]
1467
- );
1571
+ const allGroups = useMemo7(() => state.sessions.flatMap((s) => s.groups), [state.sessions]);
1468
1572
  const statusCounts = useMemo7(
1469
1573
  () => allGroups.flatMap((g) => g.tabs).flatMap((t) => t.panes).filter((p) => p.kind === "claude" && p.claudeStatus).reduce((acc, p) => {
1470
1574
  const s = p.claudeStatus;
@@ -1474,53 +1578,54 @@ var ManagerView = ({
1474
1578
  }, {}),
1475
1579
  [allGroups]
1476
1580
  );
1477
- const handleOpenAddSession = useCallback4(() => {
1478
- setMode(MODE.addSession);
1479
- }, []);
1480
- const handleAddSessionSelect = useCallback4((path) => {
1581
+ const handleOpenAddSession = useCallback5(() => {
1582
+ patch({ view: { mode: "addSession" } });
1583
+ }, [patch]);
1584
+ const handleAddSessionSelect = useCallback5((path) => {
1481
1585
  const name = basename2(path);
1482
- setSessionsState((prev) => {
1586
+ setState((prev) => {
1483
1587
  const exists2 = prev.sessions.some((s) => s.name === name);
1484
- if (exists2) return prev;
1485
1588
  return {
1486
1589
  ...prev,
1487
- sessions: [{ name, path, groups: [] }, ...prev.sessions]
1590
+ view: { mode: "split" },
1591
+ selectedSession: name,
1592
+ sessions: exists2 ? prev.sessions : [{ name, path, groups: [] }, ...prev.sessions]
1488
1593
  };
1489
1594
  });
1490
- setSelectedSession(name);
1491
- setMode(MODE.split);
1492
- }, []);
1493
- const handleCancelAddSession = useCallback4(() => {
1494
- setMode(MODE.split);
1495
- }, []);
1496
- const handleDeleteSession = useCallback4((name) => {
1497
- setPendingDeleteSession(name);
1498
- setMode(MODE.deleteSession);
1499
1595
  }, []);
1500
- const handleConfirmDelete = useCallback4(() => {
1501
- if (!pendingDeleteSession) return;
1502
- const session = sessionsState.sessions.find((s) => s.name === pendingDeleteSession);
1503
- setSessionsState((prev) => ({
1596
+ const handleCancelAddSession = useCallback5(() => {
1597
+ patch({ view: { mode: "split" } });
1598
+ }, [patch]);
1599
+ const handleDeleteSession = useCallback5(
1600
+ (name) => {
1601
+ patch({ view: { mode: "deleteSession", sessionName: name } });
1602
+ },
1603
+ [patch]
1604
+ );
1605
+ const handleConfirmDelete = useCallback5(() => {
1606
+ if (state.view.mode !== "deleteSession") return;
1607
+ const { sessionName } = state.view;
1608
+ const session = state.sessions.find((s) => s.name === sessionName);
1609
+ setState((prev) => ({
1504
1610
  ...prev,
1505
- sessions: prev.sessions.filter((s) => s.name !== pendingDeleteSession)
1611
+ view: { mode: "split" },
1612
+ sessions: prev.sessions.filter((s) => s.name !== sessionName),
1613
+ selectedSession: prev.selectedSession === sessionName ? void 0 : prev.selectedSession
1506
1614
  }));
1507
- if (resolvedSession === pendingDeleteSession) {
1508
- setSelectedSession(void 0);
1509
- }
1510
1615
  if (session) {
1511
1616
  const killAll = Promise.all(
1512
1617
  session.groups.map((g) => swallow(() => actions.killSession(g.sessionName)))
1513
1618
  );
1514
1619
  void killAll.then(() => void refresh());
1515
1620
  }
1516
- setPendingDeleteSession(void 0);
1517
- setMode(MODE.split);
1518
- }, [pendingDeleteSession, resolvedSession, sessionsState.sessions, actions, refresh]);
1519
- const handleCancelDelete = useCallback4(() => {
1520
- setPendingDeleteSession(void 0);
1521
- setMode(MODE.split);
1522
- }, []);
1523
- const handleNewSession = useCallback4(
1621
+ }, [state.view, state.sessions, actions, refresh]);
1622
+ const handleCancelDelete = useCallback5(() => {
1623
+ patch({ view: { mode: "split" } });
1624
+ }, [patch]);
1625
+ const handleNewSession = useCallback5(() => {
1626
+ patch({ view: { mode: "promptInput" } });
1627
+ }, [patch]);
1628
+ const handleOpenEditor = useCallback5(
1524
1629
  (sessionName) => {
1525
1630
  const cwd = selectedManagedSession?.path;
1526
1631
  if (!cwd) return;
@@ -1528,61 +1633,77 @@ var ManagerView = ({
1528
1633
  },
1529
1634
  [actions, selectedManagedSession]
1530
1635
  );
1531
- const handleConfirmNew = useCallback4(() => {
1532
- if (!resolvedSession) return;
1533
- const cwd = restoredCwd ?? selectedManagedSession?.path;
1534
- if (!cwd) return;
1535
- void actions.createSession(resolvedSession, cwd, pendingPrompt).then(() => void refresh());
1536
- setPendingPrompt("");
1537
- setMode(MODE.split);
1538
- }, [resolvedSession, restoredCwd, selectedManagedSession, pendingPrompt, actions, refresh]);
1539
- const handleCancelConfirm = useCallback4(() => {
1540
- setPendingPrompt("");
1541
- setMode(MODE.split);
1542
- }, []);
1543
- const handleSessionSelect = useCallback4((name) => {
1544
- setSelectedSession(name);
1545
- setFocus(FOCUS.right);
1546
- }, []);
1547
- const handleSessionCursorChange = useCallback4((name) => {
1548
- setSelectedSession(name);
1549
- }, []);
1550
- const handleNavigate = useCallback4(
1636
+ const handlePromptInputSubmit = useCallback5(
1637
+ (prompt) => {
1638
+ const cwd = selectedManagedSession?.path;
1639
+ if (!cwd) return;
1640
+ patch({ view: { mode: "confirm", prompt, cwd } });
1641
+ },
1642
+ [patch, selectedManagedSession]
1643
+ );
1644
+ const handlePromptInputCancel = useCallback5(() => {
1645
+ patch({ view: { mode: "split" } });
1646
+ }, [patch]);
1647
+ const handleConfirmNew = useCallback5(
1648
+ ({ worktree }) => {
1649
+ if (state.view.mode !== "confirm") return;
1650
+ if (!resolvedSession) return;
1651
+ void actions.createSession(resolvedSession, state.view.cwd, state.view.prompt, worktree).then(() => void refresh());
1652
+ patch({ view: { mode: "split" } });
1653
+ },
1654
+ [state.view, resolvedSession, actions, refresh, patch]
1655
+ );
1656
+ const handleCancelConfirm = useCallback5(() => {
1657
+ patch({ view: { mode: "split" } });
1658
+ }, [patch]);
1659
+ const handleSessionSelect = useCallback5(
1660
+ (name) => {
1661
+ patch({ selectedSession: name, focus: FOCUS.right });
1662
+ },
1663
+ [patch]
1664
+ );
1665
+ const handleSessionCursorChange = useCallback5(
1666
+ (name) => {
1667
+ patch({ selectedSession: name });
1668
+ },
1669
+ [patch]
1670
+ );
1671
+ const handleNavigate = useCallback5(
1551
1672
  (up) => {
1552
1673
  void actions.navigateToPane(up);
1553
1674
  },
1554
1675
  [actions]
1555
1676
  );
1556
- const handleBack = useCallback4(() => {
1557
- setFocus(FOCUS.left);
1558
- }, []);
1559
- const handleKillPane = useCallback4(
1677
+ const handleBack = useCallback5(() => {
1678
+ patch({ focus: FOCUS.left });
1679
+ }, [patch]);
1680
+ const handleKillPane = useCallback5(
1560
1681
  async (paneId) => {
1561
1682
  await swallow(() => actions.killPane(paneId));
1562
1683
  void refresh();
1563
1684
  },
1564
1685
  [actions, refresh]
1565
1686
  );
1566
- const handleHighlight = useCallback4(
1687
+ const handleHighlight = useCallback5(
1567
1688
  async (up) => {
1568
1689
  await swallow(() => actions.highlightWindow(up));
1569
1690
  },
1570
1691
  [actions]
1571
1692
  );
1572
- const handleUnhighlight = useCallback4(
1693
+ const handleUnhighlight = useCallback5(
1573
1694
  async (up) => {
1574
1695
  await swallow(() => actions.unhighlightWindow(up));
1575
1696
  },
1576
1697
  [actions]
1577
1698
  );
1578
- if (sessionsState.isLoading) {
1579
- return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: rows, children: [
1580
- /* @__PURE__ */ jsx11(Header, { title: `${APP_TITLE} v${APP_VERSION}` }),
1581
- /* @__PURE__ */ jsx11(StatusBar, { message: "Loading...", type: "info" })
1699
+ if (state.sessionsLoading) {
1700
+ return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", height: rows, children: [
1701
+ /* @__PURE__ */ jsx12(Header, { title: `${APP_TITLE} v${APP_VERSION}` }),
1702
+ /* @__PURE__ */ jsx12(StatusBar, { message: "Loading...", type: "info" })
1582
1703
  ] });
1583
1704
  }
1584
- if (mode === MODE.addSession) {
1585
- return /* @__PURE__ */ jsx11(
1705
+ if (state.view.mode === "addSession") {
1706
+ return /* @__PURE__ */ jsx12(
1586
1707
  DirectorySearchView,
1587
1708
  {
1588
1709
  directories,
@@ -1591,105 +1712,119 @@ var ManagerView = ({
1591
1712
  }
1592
1713
  );
1593
1714
  }
1594
- if (mode === MODE.deleteSession && pendingDeleteSession) {
1595
- const deleteSession = sessionsState.sessions.find((s) => s.name === pendingDeleteSession);
1715
+ if (state.view.mode === "deleteSession") {
1716
+ const { sessionName } = state.view;
1717
+ const deleteSession = state.sessions.find((s) => s.name === sessionName);
1596
1718
  const paneCount = deleteSession?.groups.reduce(
1597
1719
  (sum, g) => sum + g.tabs.reduce((s, t) => s + t.panes.length, 0),
1598
1720
  0
1599
1721
  ) ?? 0;
1600
- return /* @__PURE__ */ jsx11(
1722
+ return /* @__PURE__ */ jsx12(
1601
1723
  DeleteSessionView,
1602
1724
  {
1603
- sessionName: pendingDeleteSession,
1725
+ sessionName,
1604
1726
  paneCount,
1605
1727
  onConfirm: handleConfirmDelete,
1606
1728
  onCancel: handleCancelDelete
1607
1729
  }
1608
1730
  );
1609
1731
  }
1610
- if (mode === MODE.confirm && pendingPrompt) {
1611
- return /* @__PURE__ */ jsx11(
1732
+ if (state.view.mode === "promptInput") {
1733
+ return /* @__PURE__ */ jsx12(
1734
+ PromptInputView,
1735
+ {
1736
+ selectedDir: resolvedSession ?? "",
1737
+ onSubmit: handlePromptInputSubmit,
1738
+ onCancel: handlePromptInputCancel
1739
+ }
1740
+ );
1741
+ }
1742
+ if (state.view.mode === "confirm") {
1743
+ return /* @__PURE__ */ jsx12(
1612
1744
  ConfirmView,
1613
1745
  {
1614
1746
  selectedDir: resolvedSession ?? "",
1615
- prompt: pendingPrompt,
1747
+ prompt: state.view.prompt,
1616
1748
  onConfirm: handleConfirmNew,
1617
1749
  onCancel: handleCancelConfirm
1618
1750
  }
1619
1751
  );
1620
1752
  }
1621
- const fixedRows = 3;
1622
- const contentHeight = rows - fixedRows;
1623
- const topHeight = Math.floor(contentHeight / 2);
1753
+ const contentHeight = rows - FIXED_ROWS;
1754
+ const topHeight = Math.floor(contentHeight * TOP_HEIGHT_RATIO);
1624
1755
  const bottomHeight = contentHeight - topHeight;
1625
- const leftWidth = Math.floor(columns / 3);
1756
+ const leftWidth = Math.floor(columns * LEFT_WIDTH_RATIO);
1626
1757
  const rightWidth = columns - leftWidth;
1627
- return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: rows, children: [
1628
- /* @__PURE__ */ jsx11(Header, { title: `${APP_TITLE} - v${APP_VERSION}` }),
1629
- /* @__PURE__ */ jsxs9(Box11, { flexDirection: "row", height: topHeight, children: [
1630
- /* @__PURE__ */ jsx11(
1631
- Box11,
1758
+ return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", height: rows, children: [
1759
+ /* @__PURE__ */ jsx12(Header, { title: `${APP_TITLE} - v${APP_VERSION}` }),
1760
+ /* @__PURE__ */ jsxs10(Box12, { flexDirection: "row", height: topHeight, children: [
1761
+ /* @__PURE__ */ jsx12(
1762
+ Box12,
1632
1763
  {
1633
1764
  flexDirection: "column",
1634
1765
  width: leftWidth,
1635
1766
  borderStyle: "round",
1636
- borderColor: focus === FOCUS.left ? "green" : "gray",
1637
- children: /* @__PURE__ */ jsx11(
1767
+ borderColor: state.focus === FOCUS.left ? "green" : "gray",
1768
+ children: /* @__PURE__ */ jsx12(
1638
1769
  SessionListPanel,
1639
1770
  {
1640
- sessions: sessionsState.sessions,
1771
+ sessions: state.sessions,
1641
1772
  currentSession,
1642
- isFocused: focus === FOCUS.left,
1773
+ isFocused: state.focus === FOCUS.left,
1643
1774
  availableRows: topHeight - 2,
1644
1775
  onSelect: handleSessionSelect,
1645
1776
  onCursorChange: handleSessionCursorChange,
1646
1777
  onDeleteSession: handleDeleteSession,
1647
- onAddSession: handleOpenAddSession
1778
+ onAddSession: handleOpenAddSession,
1779
+ cursorRef: cursorsRef.current.session
1648
1780
  }
1649
1781
  )
1650
1782
  }
1651
1783
  ),
1652
- /* @__PURE__ */ jsx11(
1653
- Box11,
1784
+ /* @__PURE__ */ jsx12(
1785
+ Box12,
1654
1786
  {
1655
1787
  flexDirection: "column",
1656
1788
  width: rightWidth,
1657
1789
  borderStyle: "round",
1658
- borderColor: focus === FOCUS.right ? "green" : "gray",
1659
- children: /* @__PURE__ */ jsx11(
1790
+ borderColor: state.focus === FOCUS.right ? "green" : "gray",
1791
+ children: /* @__PURE__ */ jsx12(
1660
1792
  PaneListPanel,
1661
1793
  {
1662
1794
  selectedSession: resolvedSession,
1663
1795
  group: selectedGroup,
1664
- isFocused: focus === FOCUS.right,
1796
+ isFocused: state.focus === FOCUS.right,
1665
1797
  availableRows: topHeight - 2,
1666
1798
  onNavigate: handleNavigate,
1667
1799
  onHighlight: handleHighlight,
1668
1800
  onUnhighlight: handleUnhighlight,
1669
1801
  onBack: handleBack,
1670
1802
  onNewSession: handleNewSession,
1671
- onKillPane: handleKillPane
1803
+ onOpenEditor: handleOpenEditor,
1804
+ onKillPane: handleKillPane,
1805
+ cursorRef: cursorsRef.current.pane
1672
1806
  }
1673
1807
  )
1674
1808
  }
1675
1809
  )
1676
1810
  ] }),
1677
- /* @__PURE__ */ jsx11(
1811
+ /* @__PURE__ */ jsx12(
1678
1812
  SessionOverviewPanel,
1679
1813
  {
1680
- overallSummary: overviewResult.overallSummary,
1681
- items: overviewResult.sessions,
1814
+ overallSummary: state.overviewResult?.overallSummary ?? "",
1815
+ items: state.overviewResult?.sessions ?? [],
1682
1816
  groups: allGroups,
1683
- isLoading: overviewLoading,
1684
- isFocused: focus === FOCUS.bottom,
1817
+ isLoading: !state.overviewResult,
1818
+ isFocused: state.focus === FOCUS.bottom,
1685
1819
  availableRows: bottomHeight,
1686
- onBack: handleBack
1820
+ onBack: handleBack,
1821
+ cursorRef: cursorsRef.current.overview
1687
1822
  }
1688
1823
  ),
1689
- /* @__PURE__ */ jsx11(
1824
+ /* @__PURE__ */ jsx12(
1690
1825
  StatusBar,
1691
1826
  {
1692
- message: focus === FOCUS.left ? "\u2191/\u2193 move Enter/\u2192 select n add d delete q quit" : focus === FOCUS.right ? "\u2191/\u2193 move Enter focus n new d kill Esc/\u2190 back q quit" : "\u2191/\u2193 scroll Esc/\u2190 back q quit",
1827
+ message: state.focus === FOCUS.left ? "\u2191/\u2193 move Enter/\u2192 select Tab next n add d delete q quit" : state.focus === FOCUS.right ? "\u2191/\u2193 move Enter focus Tab next n new v vim d kill Esc/\u2190 back q quit" : "\u2191/\u2193 scroll Tab next Esc/\u2190 back q quit",
1693
1828
  statusCounts
1694
1829
  }
1695
1830
  )
@@ -1700,9 +1835,8 @@ var ManagerView = ({
1700
1835
  var createTuiCommand = ({ usecases, services, infra }) => async () => {
1701
1836
  const directories = await services.directoryScan.scan();
1702
1837
  let instance;
1703
- let pendingPrompt;
1704
- let pendingSession;
1705
- let pendingCwd;
1838
+ const snapshotRef = { current: void 0 };
1839
+ let remountState;
1706
1840
  const actions = {
1707
1841
  fetchSessions: async () => {
1708
1842
  const result = await usecases.manager.list();
@@ -1736,8 +1870,8 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1736
1870
  const groups = sessions.flatMap((s) => s.groups);
1737
1871
  return await usecases.manager.fetchOverview(groups);
1738
1872
  },
1739
- createSession: async (sessionName, cwd, prompt) => {
1740
- await usecases.manager.createSession({ sessionName, cwd, prompt });
1873
+ createSession: async (sessionName, cwd, prompt, worktree) => {
1874
+ await usecases.manager.createSession({ sessionName, cwd, prompt, worktree });
1741
1875
  },
1742
1876
  killSession: async (sessionName) => {
1743
1877
  await usecases.manager.killSession(sessionName);
@@ -1752,11 +1886,10 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1752
1886
  await usecases.manager.unhighlightWindow(up);
1753
1887
  },
1754
1888
  openEditor: (sessionName, cwd) => {
1889
+ const snapshot = snapshotRef.current;
1755
1890
  instance.unmount();
1756
1891
  const prompt = infra.editor.open();
1757
- pendingPrompt = prompt;
1758
- pendingSession = sessionName;
1759
- pendingCwd = cwd;
1892
+ remountState = { prompt, session: sessionName, cwd, snapshot };
1760
1893
  instance = renderApp();
1761
1894
  return prompt;
1762
1895
  },
@@ -1764,18 +1897,17 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1764
1897
  const target = `${up.pane.sessionName}:${String(up.pane.windowIndex)}`;
1765
1898
  await infra.tmuxCli.selectWindow(target);
1766
1899
  await infra.tmuxCli.selectPane(up.pane.paneId);
1900
+ const snapshot = snapshotRef.current;
1767
1901
  instance.unmount();
1768
1902
  await infra.tmuxCli.attachSession(up.pane.sessionName);
1903
+ remountState = { snapshot };
1769
1904
  instance = renderApp();
1770
1905
  }
1771
1906
  };
1772
1907
  const renderApp = () => {
1773
- const prompt = pendingPrompt;
1774
- const session = pendingSession;
1775
- const cwd = pendingCwd;
1776
- pendingPrompt = void 0;
1777
- pendingSession = void 0;
1778
- pendingCwd = void 0;
1908
+ const state = remountState;
1909
+ remountState = void 0;
1910
+ snapshotRef.current = void 0;
1779
1911
  const rawCwd = process.cwd();
1780
1912
  const currentSession = basename3(findMatchingDirectory(rawCwd, directories) ?? rawCwd);
1781
1913
  return render(
@@ -1783,9 +1915,8 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1783
1915
  actions,
1784
1916
  currentSession,
1785
1917
  directories,
1786
- restoredPrompt: prompt,
1787
- restoredSession: session,
1788
- restoredCwd: cwd
1918
+ remountState: state,
1919
+ snapshotRef
1789
1920
  }),
1790
1921
  { concurrent: true }
1791
1922
  );
@@ -1801,18 +1932,20 @@ var createNewCommand = ({ usecases }) => async (args) => {
1801
1932
  const { values, positionals } = parseArgs({
1802
1933
  args,
1803
1934
  options: {
1804
- dir: { type: "string" }
1935
+ dir: { type: "string" },
1936
+ worktree: { type: "boolean", default: true }
1805
1937
  },
1806
1938
  allowPositionals: true
1807
1939
  });
1808
1940
  const prompt = positionals[0];
1809
1941
  if (!prompt) {
1810
- console.error("Usage: abmux new <prompt> [--dir <path>]");
1942
+ console.error("Usage: abmux new <prompt> [--dir <path>] [--no-worktree]");
1811
1943
  process.exit(1);
1812
1944
  }
1813
1945
  const dir = values.dir ?? process.cwd();
1814
1946
  const sessionName = basename4(dir);
1815
- await usecases.manager.createSession({ sessionName, cwd: dir, prompt });
1947
+ const worktree = values.worktree ?? true;
1948
+ await usecases.manager.createSession({ sessionName, cwd: dir, prompt, worktree });
1816
1949
  console.log(`Session "${sessionName}" created.`);
1817
1950
  };
1818
1951
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmux",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cut0/abmux.git"