abmux 0.0.4 → 0.0.5

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/index.js +181 -184
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -178,15 +178,6 @@ var PANE_KIND = {
178
178
  busy: "busy"
179
179
  };
180
180
 
181
- // src/utils/PathUtils.ts
182
- var formatCwd = (cwd) => {
183
- const home = process.env["HOME"] ?? "";
184
- if (home && cwd.startsWith(home)) {
185
- return `~${cwd.slice(home.length)}`;
186
- }
187
- return cwd;
188
- };
189
-
190
181
  // src/services/session-detection-service.ts
191
182
  var BUSY_TITLES = /* @__PURE__ */ new Set([
192
183
  "nvim",
@@ -252,66 +243,44 @@ var toUnifiedPane = (pane) => {
252
243
  var createSessionDetectionService = () => ({
253
244
  groupBySession: ({ panes }) => {
254
245
  const windowKey = (pane) => `${pane.sessionName}:${String(pane.windowIndex)}`;
255
- const windowMap = /* @__PURE__ */ new Map();
256
- for (const pane of panes) {
257
- const key = windowKey(pane);
258
- const unified = toUnifiedPane(pane);
259
- const existing = windowMap.get(key);
260
- if (existing) {
261
- existing.panes = [...existing.panes, unified];
262
- if (pane.isActive) {
263
- existing.activePaneTitle = pane.title;
264
- }
265
- } else {
266
- windowMap.set(key, {
267
- sessionName: pane.sessionName,
268
- windowIndex: pane.windowIndex,
269
- cwd: formatCwd(pane.cwd),
270
- windowName: pane.windowName,
271
- activePaneTitle: pane.isActive ? pane.title : "",
272
- panes: [unified]
273
- });
246
+ const panesByWindow = Map.groupBy(panes, windowKey);
247
+ const sortPanes = (items) => items.toSorted((a, b) => {
248
+ const kindOrder = { claude: 0, available: 1, busy: 2 };
249
+ const kindDiff = kindOrder[a.kind] - kindOrder[b.kind];
250
+ if (kindDiff !== 0) return kindDiff;
251
+ if (a.kind === "claude" && b.kind === "claude") {
252
+ const statusOrder = {
253
+ "waiting-confirm": 0,
254
+ "waiting-input": 1,
255
+ thinking: 2,
256
+ "tool-running": 3,
257
+ idle: 4
258
+ };
259
+ const sa = statusOrder[a.claudeStatus ?? "idle"] ?? 4;
260
+ const sb = statusOrder[b.claudeStatus ?? "idle"] ?? 4;
261
+ if (sa !== sb) return sa - sb;
274
262
  }
275
- }
276
- const windowGroups = [...windowMap.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([, group]) => ({
277
- windowIndex: group.windowIndex,
278
- windowName: group.windowName || group.activePaneTitle || `Window ${String(group.windowIndex)}`,
279
- sessionName: group.sessionName,
280
- panes: group.panes.toSorted((a, b) => {
281
- const kindOrder = { claude: 0, available: 1, busy: 2 };
282
- const kindDiff = kindOrder[a.kind] - kindOrder[b.kind];
283
- if (kindDiff !== 0) return kindDiff;
284
- if (a.kind === "claude" && b.kind === "claude") {
285
- const statusOrder = {
286
- "waiting-confirm": 0,
287
- "waiting-input": 1,
288
- thinking: 2,
289
- "tool-running": 3,
290
- idle: 4
291
- };
292
- const sa = statusOrder[a.claudeStatus ?? "idle"] ?? 4;
293
- const sb = statusOrder[b.claudeStatus ?? "idle"] ?? 4;
294
- if (sa !== sb) return sa - sb;
295
- }
296
- return a.pane.paneIndex - b.pane.paneIndex;
297
- })
263
+ return a.pane.paneIndex - b.pane.paneIndex;
264
+ });
265
+ const windowGroups = [...panesByWindow.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([, group]) => {
266
+ const first = group[0];
267
+ const activePaneTitle = group.find((p) => p.isActive)?.title ?? "";
268
+ return {
269
+ windowIndex: first.windowIndex,
270
+ windowName: first.windowName || activePaneTitle || `Window ${String(first.windowIndex)}`,
271
+ sessionName: first.sessionName,
272
+ panes: sortPanes(group.map(toUnifiedPane))
273
+ };
274
+ });
275
+ const sessionGroups = Map.groupBy(windowGroups, (win) => win.sessionName);
276
+ return [...sessionGroups.entries()].map(([sessionName, wins]) => ({
277
+ sessionName,
278
+ tabs: wins.map(({ windowIndex, windowName, panes: p }) => ({
279
+ windowIndex,
280
+ windowName,
281
+ panes: p
282
+ }))
298
283
  }));
299
- const sessionMap = /* @__PURE__ */ new Map();
300
- for (const win of windowGroups) {
301
- const existing = sessionMap.get(win.sessionName);
302
- if (existing) {
303
- existing.push({
304
- windowIndex: win.windowIndex,
305
- windowName: win.windowName,
306
- panes: win.panes
307
- });
308
- } else {
309
- sessionMap.set(win.sessionName, [
310
- { windowIndex: win.windowIndex, windowName: win.windowName, panes: win.panes }
311
- ]);
312
- }
313
- }
314
- return [...sessionMap.entries()].map(([sessionName, tabs]) => ({ sessionName, tabs }));
315
284
  },
316
285
  detectStatusFromText
317
286
  });
@@ -396,24 +365,15 @@ var findProjects = async (dir, depth) => {
396
365
  const dirs = entries.filter(
397
366
  (e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP_DIRS.has(e.name)
398
367
  );
399
- const results = [];
400
- const childScans = [];
401
- for (const entry of dirs) {
368
+ const childScans = dirs.map((entry) => {
402
369
  const fullPath = join2(dir, entry.name);
403
- childScans.push(
404
- exists(join2(fullPath, ".git")).then(async (isProject) => {
405
- if (isProject) return [fullPath];
406
- return await findProjects(fullPath, depth + 1);
407
- })
408
- );
409
- }
370
+ return exists(join2(fullPath, ".git")).then(async (isProject) => {
371
+ if (isProject) return [fullPath];
372
+ return await findProjects(fullPath, depth + 1);
373
+ });
374
+ });
410
375
  const nested = await Promise.all(childScans);
411
- for (const paths of nested) {
412
- for (const p of paths) {
413
- results.push(p);
414
- }
415
- }
416
- return results;
376
+ return nested.flat();
417
377
  } catch {
418
378
  return [];
419
379
  }
@@ -507,7 +467,7 @@ var createUsecases = (context) => ({
507
467
  // package.json
508
468
  var package_default = {
509
469
  name: "abmux",
510
- version: "0.0.4",
470
+ version: "0.0.5",
511
471
  repository: {
512
472
  type: "git",
513
473
  url: "https://github.com/cut0/abmux.git"
@@ -618,17 +578,29 @@ var useScroll = (cursor, totalItems, availableRows) => {
618
578
  }, [cursor, totalItems, availableRows]);
619
579
  };
620
580
 
581
+ // src/utils/PathUtils.ts
582
+ var formatCwd = (cwd) => {
583
+ const home = process.env["HOME"] ?? "";
584
+ if (home && cwd.startsWith(home)) {
585
+ return `~${cwd.slice(home.length)}`;
586
+ }
587
+ return cwd;
588
+ };
589
+ var findMatchingDirectory = (path, directories) => directories.filter((dir) => path === dir || path.startsWith(dir + "/")).reduce(
590
+ (best, dir) => !best || dir.length > best.length ? dir : best,
591
+ void 0
592
+ );
593
+
621
594
  // src/components/shared/DirectorySelect.tsx
622
595
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
623
- var sortSessionGroups = (groups, currentSession) => {
624
- const current = groups.filter((g) => g.sessionName === currentSession);
625
- const rest = groups.filter((g) => g.sessionName !== currentSession);
596
+ var sortSessions = (sessions, currentSession) => {
597
+ const current = sessions.filter((s) => s.name === currentSession);
598
+ const rest = sessions.filter((s) => s.name !== currentSession);
626
599
  return [...current, ...rest];
627
600
  };
628
601
  var SessionListPanel = ({
629
- sessionGroups,
602
+ sessions,
630
603
  currentSession,
631
- sessionPathMap,
632
604
  isFocused,
633
605
  availableRows,
634
606
  onSelect,
@@ -638,30 +610,30 @@ var SessionListPanel = ({
638
610
  }) => {
639
611
  const { exit } = useApp();
640
612
  const [cursor, setCursor] = useState(0);
641
- const sortedGroups = useMemo2(
642
- () => sortSessionGroups(sessionGroups, currentSession),
643
- [sessionGroups, currentSession]
613
+ const sortedSessions = useMemo2(
614
+ () => sortSessions(sessions, currentSession),
615
+ [sessions, currentSession]
644
616
  );
645
- const sessions = useMemo2(() => sortedGroups.map((g) => g.sessionName), [sortedGroups]);
646
- const clampedCursor = cursor >= sessions.length ? Math.max(0, sessions.length - 1) : cursor;
617
+ const names = useMemo2(() => sortedSessions.map((s) => s.name), [sortedSessions]);
618
+ const clampedCursor = cursor >= names.length ? Math.max(0, names.length - 1) : cursor;
647
619
  if (clampedCursor !== cursor) {
648
620
  setCursor(clampedCursor);
649
621
  }
650
622
  const reservedLines = 1;
651
623
  const { scrollOffset, visibleCount } = useScroll(
652
624
  clampedCursor,
653
- sessions.length,
625
+ names.length,
654
626
  availableRows - reservedLines
655
627
  );
656
- const visibleSessions = sessions.slice(scrollOffset, scrollOffset + visibleCount);
628
+ const visibleSessions = sortedSessions.slice(scrollOffset, scrollOffset + visibleCount);
657
629
  const moveCursor = useCallback(
658
630
  (next) => {
659
- const clamped = Math.max(0, Math.min(sessions.length - 1, next));
631
+ const clamped = Math.max(0, Math.min(names.length - 1, next));
660
632
  setCursor(clamped);
661
- const name = sessions[clamped];
633
+ const name = names[clamped];
662
634
  if (name) onCursorChange(name);
663
635
  },
664
- [sessions, onCursorChange]
636
+ [names, onCursorChange]
665
637
  );
666
638
  useInput(
667
639
  (input, key) => {
@@ -678,12 +650,12 @@ var SessionListPanel = ({
678
650
  return;
679
651
  }
680
652
  if (key.return || key.rightArrow) {
681
- const name = sessions[clampedCursor];
653
+ const name = names[clampedCursor];
682
654
  if (name) onSelect(name);
683
655
  return;
684
656
  }
685
657
  if (input === "d" && onDeleteSession) {
686
- const name = sessions[clampedCursor];
658
+ const name = names[clampedCursor];
687
659
  if (name) onDeleteSession(name);
688
660
  return;
689
661
  }
@@ -701,20 +673,19 @@ var SessionListPanel = ({
701
673
  "(",
702
674
  clampedCursor + 1,
703
675
  "/",
704
- sessions.length,
676
+ names.length,
705
677
  ")"
706
678
  ] })
707
679
  ] }),
708
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleSessions.map((name, i) => {
680
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleSessions.map((session, i) => {
709
681
  const globalIndex = scrollOffset + i;
710
682
  const isHighlighted = globalIndex === clampedCursor;
711
- const isCurrent = name === currentSession;
712
- const path = sessionPathMap.get(name);
683
+ const isCurrent = session.name === currentSession;
713
684
  return /* @__PURE__ */ jsxs(Box3, { paddingLeft: 1, gap: 1, children: [
714
685
  /* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
715
- /* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : "cyan", bold: isHighlighted, wrap: "truncate", children: path ? formatCwd(path) : name }),
686
+ /* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : "cyan", bold: isHighlighted, wrap: "truncate", children: session.path ? formatCwd(session.path) : session.name }),
716
687
  isCurrent && /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "(cwd)" })
717
- ] }, name);
688
+ ] }, session.name);
718
689
  }) })
719
690
  ] });
720
691
  };
@@ -1122,14 +1093,16 @@ var POLL_INTERVAL = 3e3;
1122
1093
  var ManagerView = ({
1123
1094
  actions,
1124
1095
  currentSession,
1125
- currentCwd,
1126
1096
  directories,
1127
- sessionCwdMap,
1128
1097
  restoredPrompt,
1129
- restoredSession
1098
+ restoredSession,
1099
+ restoredCwd
1130
1100
  }) => {
1131
1101
  const { rows, columns } = useTerminalSize();
1132
- const [fetchState, setFetchState] = useState5({ data: [], isLoading: true });
1102
+ const [sessionsState, setSessionsState] = useState5({
1103
+ sessions: [],
1104
+ isLoading: true
1105
+ });
1133
1106
  const [mode, setMode] = useState5(restoredPrompt ? MODE.confirm : MODE.split);
1134
1107
  const [focus, setFocus] = useState5(FOCUS.left);
1135
1108
  const [selectedSession, setSelectedSession] = useState5(restoredSession);
@@ -1137,17 +1110,16 @@ var ManagerView = ({
1137
1110
  const [pendingDeleteSession, setPendingDeleteSession] = useState5(void 0);
1138
1111
  const refresh = useCallback3(async () => {
1139
1112
  try {
1140
- const groups = await actions.fetchSessions();
1141
- const knownNames = new Set(groups.map((g) => g.sessionName));
1142
- const missing = [];
1143
- for (const name of sessionCwdMap.keys()) {
1144
- if (!knownNames.has(name)) {
1145
- missing.push({ sessionName: name, tabs: [] });
1146
- }
1147
- }
1148
- setFetchState({ data: [...missing, ...groups], isLoading: false });
1113
+ const fetched = await actions.fetchSessions();
1114
+ setSessionsState((prev) => {
1115
+ const fetchedNames = new Set(fetched.map((s) => s.name));
1116
+ const userOnly = prev.sessions.filter(
1117
+ (s) => !fetchedNames.has(s.name) && s.groups.length === 0
1118
+ );
1119
+ return { sessions: [...userOnly, ...fetched], isLoading: false };
1120
+ });
1149
1121
  } catch {
1150
- setFetchState((prev) => ({ ...prev, isLoading: false }));
1122
+ setSessionsState((prev) => ({ ...prev, isLoading: false }));
1151
1123
  }
1152
1124
  }, [actions]);
1153
1125
  useEffect2(() => {
@@ -1159,40 +1131,34 @@ var ManagerView = ({
1159
1131
  clearInterval(timer);
1160
1132
  };
1161
1133
  }, [refresh]);
1162
- const resolvedSession = selectedSession ?? fetchState.data[0]?.sessionName;
1163
- const selectedGroup = useMemo5(
1164
- () => fetchState.data.find((g) => g.sessionName === resolvedSession),
1165
- [fetchState.data, resolvedSession]
1134
+ const resolvedSession = selectedSession ?? sessionsState.sessions[0]?.name;
1135
+ const selectedManagedSession = useMemo5(
1136
+ () => sessionsState.sessions.find((s) => s.name === resolvedSession),
1137
+ [sessionsState.sessions, resolvedSession]
1166
1138
  );
1167
- const sessionPathMap = useMemo5(() => {
1168
- const map = /* @__PURE__ */ new Map();
1169
- for (const group of fetchState.data) {
1170
- const fromMap = sessionCwdMap.get(group.sessionName);
1171
- const fromPane = group.tabs[0]?.panes[0]?.pane.cwd;
1172
- const path = fromMap ?? fromPane;
1173
- if (path) map.set(group.sessionName, path);
1174
- }
1175
- return map;
1176
- }, [fetchState.data]);
1139
+ const selectedGroup = useMemo5(() => {
1140
+ if (!selectedManagedSession) return void 0;
1141
+ return {
1142
+ sessionName: selectedManagedSession.name,
1143
+ tabs: selectedManagedSession.groups.flatMap((g) => g.tabs)
1144
+ };
1145
+ }, [selectedManagedSession]);
1177
1146
  const handleOpenAddSession = useCallback3(() => {
1178
1147
  setMode(MODE.addSession);
1179
1148
  }, []);
1180
- const handleAddSessionSelect = useCallback3(
1181
- (path) => {
1182
- const name = basename2(path);
1183
- sessionCwdMap.set(name, path);
1184
- const exists2 = fetchState.data.some((g) => g.sessionName === name);
1185
- if (!exists2) {
1186
- setFetchState((prev) => ({
1187
- ...prev,
1188
- data: [{ sessionName: name, tabs: [] }, ...prev.data]
1189
- }));
1190
- }
1191
- setSelectedSession(name);
1192
- setMode(MODE.split);
1193
- },
1194
- [fetchState.data]
1195
- );
1149
+ const handleAddSessionSelect = useCallback3((path) => {
1150
+ const name = basename2(path);
1151
+ setSessionsState((prev) => {
1152
+ const exists2 = prev.sessions.some((s) => s.name === name);
1153
+ if (exists2) return prev;
1154
+ return {
1155
+ ...prev,
1156
+ sessions: [{ name, path, groups: [] }, ...prev.sessions]
1157
+ };
1158
+ });
1159
+ setSelectedSession(name);
1160
+ setMode(MODE.split);
1161
+ }, []);
1196
1162
  const handleCancelAddSession = useCallback3(() => {
1197
1163
  setMode(MODE.split);
1198
1164
  }, []);
@@ -1202,32 +1168,43 @@ var ManagerView = ({
1202
1168
  }, []);
1203
1169
  const handleConfirmDelete = useCallback3(() => {
1204
1170
  if (!pendingDeleteSession) return;
1205
- sessionCwdMap.delete(pendingDeleteSession);
1171
+ const session = sessionsState.sessions.find((s) => s.name === pendingDeleteSession);
1172
+ setSessionsState((prev) => ({
1173
+ ...prev,
1174
+ sessions: prev.sessions.filter((s) => s.name !== pendingDeleteSession)
1175
+ }));
1206
1176
  if (resolvedSession === pendingDeleteSession) {
1207
1177
  setSelectedSession(void 0);
1208
1178
  }
1209
- void swallow(() => actions.killSession(pendingDeleteSession)).then(() => void refresh());
1179
+ if (session) {
1180
+ const killAll = Promise.all(
1181
+ session.groups.map((g) => swallow(() => actions.killSession(g.sessionName)))
1182
+ );
1183
+ void killAll.then(() => void refresh());
1184
+ }
1210
1185
  setPendingDeleteSession(void 0);
1211
1186
  setMode(MODE.split);
1212
- }, [pendingDeleteSession, resolvedSession, actions, refresh]);
1187
+ }, [pendingDeleteSession, resolvedSession, sessionsState.sessions, actions, refresh]);
1213
1188
  const handleCancelDelete = useCallback3(() => {
1214
1189
  setPendingDeleteSession(void 0);
1215
1190
  setMode(MODE.split);
1216
1191
  }, []);
1217
1192
  const handleNewSession = useCallback3(
1218
1193
  (sessionName) => {
1219
- actions.openEditor(sessionName);
1194
+ const cwd = selectedManagedSession?.path;
1195
+ if (!cwd) return;
1196
+ actions.openEditor(sessionName, cwd);
1220
1197
  },
1221
- [actions]
1198
+ [actions, selectedManagedSession]
1222
1199
  );
1223
1200
  const handleConfirmNew = useCallback3(() => {
1224
1201
  if (!resolvedSession) return;
1225
- const existingCwd = selectedGroup?.tabs[0]?.panes[0]?.pane.cwd;
1226
- const cwd = sessionCwdMap.get(resolvedSession) ?? existingCwd ?? currentCwd;
1202
+ const cwd = restoredCwd ?? selectedManagedSession?.path;
1203
+ if (!cwd) return;
1227
1204
  void actions.createSession(resolvedSession, cwd, pendingPrompt).then(() => void refresh());
1228
1205
  setPendingPrompt("");
1229
1206
  setMode(MODE.split);
1230
- }, [resolvedSession, selectedGroup, currentCwd, pendingPrompt, actions, refresh]);
1207
+ }, [resolvedSession, restoredCwd, selectedManagedSession, pendingPrompt, actions, refresh]);
1231
1208
  const handleCancelConfirm = useCallback3(() => {
1232
1209
  setPendingPrompt("");
1233
1210
  setMode(MODE.split);
@@ -1267,7 +1244,7 @@ var ManagerView = ({
1267
1244
  },
1268
1245
  [actions]
1269
1246
  );
1270
- if (fetchState.isLoading) {
1247
+ if (sessionsState.isLoading) {
1271
1248
  return /* @__PURE__ */ jsxs7(Box10, { flexDirection: "column", height: rows, children: [
1272
1249
  /* @__PURE__ */ jsx10(Header, { title: `${APP_TITLE} v${APP_VERSION}` }),
1273
1250
  /* @__PURE__ */ jsx10(StatusBar, { message: "Loading...", type: "info" })
@@ -1284,8 +1261,11 @@ var ManagerView = ({
1284
1261
  );
1285
1262
  }
1286
1263
  if (mode === MODE.deleteSession && pendingDeleteSession) {
1287
- const deleteGroup = fetchState.data.find((g) => g.sessionName === pendingDeleteSession);
1288
- const paneCount = deleteGroup?.tabs.reduce((sum, t) => sum + t.panes.length, 0) ?? 0;
1264
+ const deleteSession = sessionsState.sessions.find((s) => s.name === pendingDeleteSession);
1265
+ const paneCount = deleteSession?.groups.reduce(
1266
+ (sum, g) => sum + g.tabs.reduce((s, t) => s + t.panes.length, 0),
1267
+ 0
1268
+ ) ?? 0;
1289
1269
  return /* @__PURE__ */ jsx10(
1290
1270
  DeleteSessionView,
1291
1271
  {
@@ -1323,9 +1303,8 @@ var ManagerView = ({
1323
1303
  children: /* @__PURE__ */ jsx10(
1324
1304
  SessionListPanel,
1325
1305
  {
1326
- sessionGroups: fetchState.data,
1306
+ sessions: sessionsState.sessions,
1327
1307
  currentSession,
1328
- sessionPathMap,
1329
1308
  isFocused: focus === FOCUS.left,
1330
1309
  availableRows: panelHeight,
1331
1310
  onSelect: handleSessionSelect,
@@ -1368,25 +1347,38 @@ var ManagerView = ({
1368
1347
  // src/cli/tui-command.ts
1369
1348
  var createTuiCommand = ({ usecases, services, infra }) => async () => {
1370
1349
  const directories = await services.directoryScan.scan();
1371
- const sessionCwdMap = /* @__PURE__ */ new Map();
1372
1350
  let instance;
1373
1351
  let pendingPrompt;
1374
1352
  let pendingSession;
1353
+ let pendingCwd;
1375
1354
  const actions = {
1376
1355
  fetchSessions: async () => {
1377
1356
  const result = await usecases.manager.list();
1378
- return await Promise.all(
1379
- result.sessionGroups.map(async (group) => ({
1380
- sessionName: group.sessionName,
1381
- tabs: await Promise.all(
1382
- group.tabs.map(async (tab) => ({
1383
- windowIndex: tab.windowIndex,
1384
- windowName: tab.windowName,
1385
- panes: await Promise.all(tab.panes.map((up) => usecases.manager.enrichStatus(up)))
1386
- }))
1387
- )
1388
- }))
1357
+ const resolved = await Promise.all(
1358
+ result.sessionGroups.map(async (group) => {
1359
+ const enrichedGroup = {
1360
+ sessionName: group.sessionName,
1361
+ tabs: await Promise.all(
1362
+ group.tabs.map(async (tab) => ({
1363
+ windowIndex: tab.windowIndex,
1364
+ windowName: tab.windowName,
1365
+ panes: await Promise.all(
1366
+ tab.panes.map((up) => usecases.manager.enrichStatus(up))
1367
+ )
1368
+ }))
1369
+ )
1370
+ };
1371
+ const paneCwd = group.tabs[0]?.panes[0]?.pane.cwd ?? "";
1372
+ const path = findMatchingDirectory(paneCwd, directories) ?? paneCwd;
1373
+ return { path, group: enrichedGroup };
1374
+ })
1389
1375
  );
1376
+ const grouped = Map.groupBy(resolved, (item) => item.path || item.group.sessionName);
1377
+ return [...grouped.entries()].map(([key, items]) => ({
1378
+ name: basename3(key) || items[0].group.sessionName,
1379
+ path: items[0].path,
1380
+ groups: items.map((item) => item.group)
1381
+ }));
1390
1382
  },
1391
1383
  createSession: async (sessionName, cwd, prompt) => {
1392
1384
  await usecases.manager.createSession({ sessionName, cwd, prompt });
@@ -1403,11 +1395,12 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1403
1395
  unhighlightWindow: async (up) => {
1404
1396
  await usecases.manager.unhighlightWindow(up);
1405
1397
  },
1406
- openEditor: (sessionName) => {
1398
+ openEditor: (sessionName, cwd) => {
1407
1399
  instance.unmount();
1408
1400
  const prompt = infra.editor.open();
1409
1401
  pendingPrompt = prompt;
1410
1402
  pendingSession = sessionName;
1403
+ pendingCwd = cwd;
1411
1404
  instance = renderApp();
1412
1405
  return prompt;
1413
1406
  },
@@ -1423,17 +1416,20 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1423
1416
  const renderApp = () => {
1424
1417
  const prompt = pendingPrompt;
1425
1418
  const session = pendingSession;
1419
+ const cwd = pendingCwd;
1426
1420
  pendingPrompt = void 0;
1427
1421
  pendingSession = void 0;
1422
+ pendingCwd = void 0;
1423
+ const rawCwd = process.cwd();
1424
+ const currentSession = basename3(findMatchingDirectory(rawCwd, directories) ?? rawCwd);
1428
1425
  return render(
1429
1426
  createElement(ManagerView, {
1430
1427
  actions,
1431
- currentSession: basename3(process.cwd()),
1432
- currentCwd: process.cwd(),
1428
+ currentSession,
1433
1429
  directories,
1434
- sessionCwdMap,
1435
1430
  restoredPrompt: prompt,
1436
- restoredSession: session
1431
+ restoredSession: session,
1432
+ restoredCwd: cwd
1437
1433
  }),
1438
1434
  { concurrent: true }
1439
1435
  );
@@ -1491,10 +1487,11 @@ var createListCommand = ({ usecases }) => async () => {
1491
1487
  console.log("No sessions found.");
1492
1488
  return;
1493
1489
  }
1494
- for (const group of result.sessionGroups) {
1490
+ const lines = result.sessionGroups.map((group) => {
1495
1491
  const paneCount = group.tabs.reduce((sum, t) => sum + t.panes.length, 0);
1496
- console.log(`${group.sessionName} (${String(paneCount)} panes)`);
1497
- }
1492
+ return `${group.sessionName} (${String(paneCount)} panes)`;
1493
+ });
1494
+ console.log(lines.join("\n"));
1498
1495
  };
1499
1496
 
1500
1497
  // src/cli/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmux",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cut0/abmux.git"