abmux 0.0.3 → 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 +188 -170
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -228,13 +228,6 @@ var detectStatusFromText = (paneText) => {
228
228
  }
229
229
  return SESSION_STATUS.idle;
230
230
  };
231
- var formatCwd = (cwd) => {
232
- const home = process.env["HOME"] ?? "";
233
- if (home && cwd.startsWith(home)) {
234
- return `~${cwd.slice(home.length)}`;
235
- }
236
- return cwd;
237
- };
238
231
  var toUnifiedPane = (pane) => {
239
232
  const kind = classifyPane(pane);
240
233
  if (kind === PANE_KIND.claude) {
@@ -250,66 +243,44 @@ var toUnifiedPane = (pane) => {
250
243
  var createSessionDetectionService = () => ({
251
244
  groupBySession: ({ panes }) => {
252
245
  const windowKey = (pane) => `${pane.sessionName}:${String(pane.windowIndex)}`;
253
- const windowMap = /* @__PURE__ */ new Map();
254
- for (const pane of panes) {
255
- const key = windowKey(pane);
256
- const unified = toUnifiedPane(pane);
257
- const existing = windowMap.get(key);
258
- if (existing) {
259
- existing.panes = [...existing.panes, unified];
260
- if (pane.isActive) {
261
- existing.activePaneTitle = pane.title;
262
- }
263
- } else {
264
- windowMap.set(key, {
265
- sessionName: pane.sessionName,
266
- windowIndex: pane.windowIndex,
267
- cwd: formatCwd(pane.cwd),
268
- windowName: pane.windowName,
269
- activePaneTitle: pane.isActive ? pane.title : "",
270
- panes: [unified]
271
- });
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;
272
262
  }
273
- }
274
- const windowGroups = [...windowMap.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([, group]) => ({
275
- windowIndex: group.windowIndex,
276
- windowName: group.windowName || group.activePaneTitle || `Window ${String(group.windowIndex)}`,
277
- sessionName: group.sessionName,
278
- panes: group.panes.toSorted((a, b) => {
279
- const kindOrder = { claude: 0, available: 1, busy: 2 };
280
- const kindDiff = kindOrder[a.kind] - kindOrder[b.kind];
281
- if (kindDiff !== 0) return kindDiff;
282
- if (a.kind === "claude" && b.kind === "claude") {
283
- const statusOrder = {
284
- "waiting-confirm": 0,
285
- "waiting-input": 1,
286
- thinking: 2,
287
- "tool-running": 3,
288
- idle: 4
289
- };
290
- const sa = statusOrder[a.claudeStatus ?? "idle"] ?? 4;
291
- const sb = statusOrder[b.claudeStatus ?? "idle"] ?? 4;
292
- if (sa !== sb) return sa - sb;
293
- }
294
- return a.pane.paneIndex - b.pane.paneIndex;
295
- })
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
+ }))
296
283
  }));
297
- const sessionMap = /* @__PURE__ */ new Map();
298
- for (const win of windowGroups) {
299
- const existing = sessionMap.get(win.sessionName);
300
- if (existing) {
301
- existing.push({
302
- windowIndex: win.windowIndex,
303
- windowName: win.windowName,
304
- panes: win.panes
305
- });
306
- } else {
307
- sessionMap.set(win.sessionName, [
308
- { windowIndex: win.windowIndex, windowName: win.windowName, panes: win.panes }
309
- ]);
310
- }
311
- }
312
- return [...sessionMap.entries()].map(([sessionName, tabs]) => ({ sessionName, tabs }));
313
284
  },
314
285
  detectStatusFromText
315
286
  });
@@ -394,24 +365,15 @@ var findProjects = async (dir, depth) => {
394
365
  const dirs = entries.filter(
395
366
  (e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP_DIRS.has(e.name)
396
367
  );
397
- const results = [];
398
- const childScans = [];
399
- for (const entry of dirs) {
368
+ const childScans = dirs.map((entry) => {
400
369
  const fullPath = join2(dir, entry.name);
401
- childScans.push(
402
- exists(join2(fullPath, ".git")).then(async (isProject) => {
403
- if (isProject) return [fullPath];
404
- return await findProjects(fullPath, depth + 1);
405
- })
406
- );
407
- }
370
+ return exists(join2(fullPath, ".git")).then(async (isProject) => {
371
+ if (isProject) return [fullPath];
372
+ return await findProjects(fullPath, depth + 1);
373
+ });
374
+ });
408
375
  const nested = await Promise.all(childScans);
409
- for (const paths of nested) {
410
- for (const p of paths) {
411
- results.push(p);
412
- }
413
- }
414
- return results;
376
+ return nested.flat();
415
377
  } catch {
416
378
  return [];
417
379
  }
@@ -505,7 +467,7 @@ var createUsecases = (context) => ({
505
467
  // package.json
506
468
  var package_default = {
507
469
  name: "abmux",
508
- version: "0.0.3",
470
+ version: "0.0.5",
509
471
  repository: {
510
472
  type: "git",
511
473
  url: "https://github.com/cut0/abmux.git"
@@ -576,7 +538,7 @@ import { createElement } from "react";
576
538
  // src/components/ManagerView.tsx
577
539
  import { basename as basename2 } from "node:path";
578
540
  import { Box as Box10, Text as Text10 } from "ink";
579
- import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo5, useRef as useRef2, useState as useState5 } from "react";
541
+ import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo5, useState as useState5 } from "react";
580
542
 
581
543
  // src/components/shared/Header.tsx
582
544
  import { Box, Text } from "ink";
@@ -616,15 +578,28 @@ var useScroll = (cursor, totalItems, availableRows) => {
616
578
  }, [cursor, totalItems, availableRows]);
617
579
  };
618
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
+
619
594
  // src/components/shared/DirectorySelect.tsx
620
595
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
621
- var sortSessionGroups = (groups, currentSession) => {
622
- const current = groups.filter((g) => g.sessionName === currentSession);
623
- 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);
624
599
  return [...current, ...rest];
625
600
  };
626
601
  var SessionListPanel = ({
627
- sessionGroups,
602
+ sessions,
628
603
  currentSession,
629
604
  isFocused,
630
605
  availableRows,
@@ -635,30 +610,30 @@ var SessionListPanel = ({
635
610
  }) => {
636
611
  const { exit } = useApp();
637
612
  const [cursor, setCursor] = useState(0);
638
- const sortedGroups = useMemo2(
639
- () => sortSessionGroups(sessionGroups, currentSession),
640
- [sessionGroups, currentSession]
613
+ const sortedSessions = useMemo2(
614
+ () => sortSessions(sessions, currentSession),
615
+ [sessions, currentSession]
641
616
  );
642
- const sessions = useMemo2(() => sortedGroups.map((g) => g.sessionName), [sortedGroups]);
643
- 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;
644
619
  if (clampedCursor !== cursor) {
645
620
  setCursor(clampedCursor);
646
621
  }
647
622
  const reservedLines = 1;
648
623
  const { scrollOffset, visibleCount } = useScroll(
649
624
  clampedCursor,
650
- sessions.length,
625
+ names.length,
651
626
  availableRows - reservedLines
652
627
  );
653
- const visibleSessions = sessions.slice(scrollOffset, scrollOffset + visibleCount);
628
+ const visibleSessions = sortedSessions.slice(scrollOffset, scrollOffset + visibleCount);
654
629
  const moveCursor = useCallback(
655
630
  (next) => {
656
- const clamped = Math.max(0, Math.min(sessions.length - 1, next));
631
+ const clamped = Math.max(0, Math.min(names.length - 1, next));
657
632
  setCursor(clamped);
658
- const name = sessions[clamped];
633
+ const name = names[clamped];
659
634
  if (name) onCursorChange(name);
660
635
  },
661
- [sessions, onCursorChange]
636
+ [names, onCursorChange]
662
637
  );
663
638
  useInput(
664
639
  (input, key) => {
@@ -675,12 +650,12 @@ var SessionListPanel = ({
675
650
  return;
676
651
  }
677
652
  if (key.return || key.rightArrow) {
678
- const name = sessions[clampedCursor];
653
+ const name = names[clampedCursor];
679
654
  if (name) onSelect(name);
680
655
  return;
681
656
  }
682
657
  if (input === "d" && onDeleteSession) {
683
- const name = sessions[clampedCursor];
658
+ const name = names[clampedCursor];
684
659
  if (name) onDeleteSession(name);
685
660
  return;
686
661
  }
@@ -698,19 +673,19 @@ var SessionListPanel = ({
698
673
  "(",
699
674
  clampedCursor + 1,
700
675
  "/",
701
- sessions.length,
676
+ names.length,
702
677
  ")"
703
678
  ] })
704
679
  ] }),
705
- /* @__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) => {
706
681
  const globalIndex = scrollOffset + i;
707
682
  const isHighlighted = globalIndex === clampedCursor;
708
- const isCurrent = name === currentSession;
683
+ const isCurrent = session.name === currentSession;
709
684
  return /* @__PURE__ */ jsxs(Box3, { paddingLeft: 1, gap: 1, children: [
710
685
  /* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
711
- /* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : "cyan", bold: isHighlighted, wrap: "truncate", children: name }),
686
+ /* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : "cyan", bold: isHighlighted, wrap: "truncate", children: session.path ? formatCwd(session.path) : session.name }),
712
687
  isCurrent && /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "(cwd)" })
713
- ] }, name);
688
+ ] }, session.name);
714
689
  }) })
715
690
  ] });
716
691
  };
@@ -1118,32 +1093,33 @@ var POLL_INTERVAL = 3e3;
1118
1093
  var ManagerView = ({
1119
1094
  actions,
1120
1095
  currentSession,
1121
- currentCwd,
1122
1096
  directories,
1123
1097
  restoredPrompt,
1124
- restoredSession
1098
+ restoredSession,
1099
+ restoredCwd
1125
1100
  }) => {
1126
1101
  const { rows, columns } = useTerminalSize();
1127
- const [fetchState, setFetchState] = useState5({ data: [], isLoading: true });
1102
+ const [sessionsState, setSessionsState] = useState5({
1103
+ sessions: [],
1104
+ isLoading: true
1105
+ });
1128
1106
  const [mode, setMode] = useState5(restoredPrompt ? MODE.confirm : MODE.split);
1129
1107
  const [focus, setFocus] = useState5(FOCUS.left);
1130
1108
  const [selectedSession, setSelectedSession] = useState5(restoredSession);
1131
1109
  const [pendingPrompt, setPendingPrompt] = useState5(restoredPrompt ?? "");
1132
1110
  const [pendingDeleteSession, setPendingDeleteSession] = useState5(void 0);
1133
- const sessionCwdMap = useRef2(/* @__PURE__ */ new Map());
1134
1111
  const refresh = useCallback3(async () => {
1135
1112
  try {
1136
- const groups = await actions.fetchSessions();
1137
- const knownNames = new Set(groups.map((g) => g.sessionName));
1138
- const missing = [];
1139
- for (const name of sessionCwdMap.current.keys()) {
1140
- if (!knownNames.has(name)) {
1141
- missing.push({ sessionName: name, tabs: [] });
1142
- }
1143
- }
1144
- 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
+ });
1145
1121
  } catch {
1146
- setFetchState((prev) => ({ ...prev, isLoading: false }));
1122
+ setSessionsState((prev) => ({ ...prev, isLoading: false }));
1147
1123
  }
1148
1124
  }, [actions]);
1149
1125
  useEffect2(() => {
@@ -1155,30 +1131,34 @@ var ManagerView = ({
1155
1131
  clearInterval(timer);
1156
1132
  };
1157
1133
  }, [refresh]);
1158
- const resolvedSession = selectedSession ?? fetchState.data[0]?.sessionName;
1159
- const selectedGroup = useMemo5(
1160
- () => fetchState.data.find((g) => g.sessionName === resolvedSession),
1161
- [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]
1162
1138
  );
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]);
1163
1146
  const handleOpenAddSession = useCallback3(() => {
1164
1147
  setMode(MODE.addSession);
1165
1148
  }, []);
1166
- const handleAddSessionSelect = useCallback3(
1167
- (path) => {
1168
- const name = basename2(path);
1169
- sessionCwdMap.current.set(name, path);
1170
- const exists2 = fetchState.data.some((g) => g.sessionName === name);
1171
- if (!exists2) {
1172
- setFetchState((prev) => ({
1173
- ...prev,
1174
- data: [{ sessionName: name, tabs: [] }, ...prev.data]
1175
- }));
1176
- }
1177
- setSelectedSession(name);
1178
- setMode(MODE.split);
1179
- },
1180
- [fetchState.data]
1181
- );
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
+ }, []);
1182
1162
  const handleCancelAddSession = useCallback3(() => {
1183
1163
  setMode(MODE.split);
1184
1164
  }, []);
@@ -1188,31 +1168,43 @@ var ManagerView = ({
1188
1168
  }, []);
1189
1169
  const handleConfirmDelete = useCallback3(() => {
1190
1170
  if (!pendingDeleteSession) return;
1191
- sessionCwdMap.current.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
+ }));
1192
1176
  if (resolvedSession === pendingDeleteSession) {
1193
1177
  setSelectedSession(void 0);
1194
1178
  }
1195
- 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
+ }
1196
1185
  setPendingDeleteSession(void 0);
1197
1186
  setMode(MODE.split);
1198
- }, [pendingDeleteSession, resolvedSession, actions, refresh]);
1187
+ }, [pendingDeleteSession, resolvedSession, sessionsState.sessions, actions, refresh]);
1199
1188
  const handleCancelDelete = useCallback3(() => {
1200
1189
  setPendingDeleteSession(void 0);
1201
1190
  setMode(MODE.split);
1202
1191
  }, []);
1203
1192
  const handleNewSession = useCallback3(
1204
1193
  (sessionName) => {
1205
- actions.openEditor(sessionName);
1194
+ const cwd = selectedManagedSession?.path;
1195
+ if (!cwd) return;
1196
+ actions.openEditor(sessionName, cwd);
1206
1197
  },
1207
- [actions]
1198
+ [actions, selectedManagedSession]
1208
1199
  );
1209
1200
  const handleConfirmNew = useCallback3(() => {
1210
1201
  if (!resolvedSession) return;
1211
- const cwd = sessionCwdMap.current.get(resolvedSession) ?? currentCwd;
1202
+ const cwd = restoredCwd ?? selectedManagedSession?.path;
1203
+ if (!cwd) return;
1212
1204
  void actions.createSession(resolvedSession, cwd, pendingPrompt).then(() => void refresh());
1213
1205
  setPendingPrompt("");
1214
1206
  setMode(MODE.split);
1215
- }, [resolvedSession, currentCwd, pendingPrompt, actions, refresh]);
1207
+ }, [resolvedSession, restoredCwd, selectedManagedSession, pendingPrompt, actions, refresh]);
1216
1208
  const handleCancelConfirm = useCallback3(() => {
1217
1209
  setPendingPrompt("");
1218
1210
  setMode(MODE.split);
@@ -1226,7 +1218,7 @@ var ManagerView = ({
1226
1218
  }, []);
1227
1219
  const handleNavigate = useCallback3(
1228
1220
  (up) => {
1229
- actions.attachSession(up.pane.sessionName);
1221
+ void actions.navigateToPane(up);
1230
1222
  },
1231
1223
  [actions]
1232
1224
  );
@@ -1252,7 +1244,7 @@ var ManagerView = ({
1252
1244
  },
1253
1245
  [actions]
1254
1246
  );
1255
- if (fetchState.isLoading) {
1247
+ if (sessionsState.isLoading) {
1256
1248
  return /* @__PURE__ */ jsxs7(Box10, { flexDirection: "column", height: rows, children: [
1257
1249
  /* @__PURE__ */ jsx10(Header, { title: `${APP_TITLE} v${APP_VERSION}` }),
1258
1250
  /* @__PURE__ */ jsx10(StatusBar, { message: "Loading...", type: "info" })
@@ -1269,8 +1261,11 @@ var ManagerView = ({
1269
1261
  );
1270
1262
  }
1271
1263
  if (mode === MODE.deleteSession && pendingDeleteSession) {
1272
- const deleteGroup = fetchState.data.find((g) => g.sessionName === pendingDeleteSession);
1273
- 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;
1274
1269
  return /* @__PURE__ */ jsx10(
1275
1270
  DeleteSessionView,
1276
1271
  {
@@ -1308,7 +1303,7 @@ var ManagerView = ({
1308
1303
  children: /* @__PURE__ */ jsx10(
1309
1304
  SessionListPanel,
1310
1305
  {
1311
- sessionGroups: fetchState.data,
1306
+ sessions: sessionsState.sessions,
1312
1307
  currentSession,
1313
1308
  isFocused: focus === FOCUS.left,
1314
1309
  availableRows: panelHeight,
@@ -1355,21 +1350,35 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1355
1350
  let instance;
1356
1351
  let pendingPrompt;
1357
1352
  let pendingSession;
1353
+ let pendingCwd;
1358
1354
  const actions = {
1359
1355
  fetchSessions: async () => {
1360
1356
  const result = await usecases.manager.list();
1361
- return await Promise.all(
1362
- result.sessionGroups.map(async (group) => ({
1363
- sessionName: group.sessionName,
1364
- tabs: await Promise.all(
1365
- group.tabs.map(async (tab) => ({
1366
- windowIndex: tab.windowIndex,
1367
- windowName: tab.windowName,
1368
- panes: await Promise.all(tab.panes.map((up) => usecases.manager.enrichStatus(up)))
1369
- }))
1370
- )
1371
- }))
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
+ })
1372
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
+ }));
1373
1382
  },
1374
1383
  createSession: async (sessionName, cwd, prompt) => {
1375
1384
  await usecases.manager.createSession({ sessionName, cwd, prompt });
@@ -1386,33 +1395,41 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1386
1395
  unhighlightWindow: async (up) => {
1387
1396
  await usecases.manager.unhighlightWindow(up);
1388
1397
  },
1389
- openEditor: (sessionName) => {
1398
+ openEditor: (sessionName, cwd) => {
1390
1399
  instance.unmount();
1391
1400
  const prompt = infra.editor.open();
1392
1401
  pendingPrompt = prompt;
1393
1402
  pendingSession = sessionName;
1403
+ pendingCwd = cwd;
1394
1404
  instance = renderApp();
1395
1405
  return prompt;
1396
1406
  },
1397
- attachSession: (sessionName) => {
1407
+ navigateToPane: async (up) => {
1408
+ const target = `${up.pane.sessionName}:${String(up.pane.windowIndex)}`;
1409
+ await infra.tmuxCli.selectWindow(target);
1410
+ await infra.tmuxCli.selectPane(up.pane.paneId);
1398
1411
  instance.unmount();
1399
- void infra.tmuxCli.attachSession(sessionName);
1412
+ await infra.tmuxCli.attachSession(up.pane.sessionName);
1400
1413
  instance = renderApp();
1401
1414
  }
1402
1415
  };
1403
1416
  const renderApp = () => {
1404
1417
  const prompt = pendingPrompt;
1405
1418
  const session = pendingSession;
1419
+ const cwd = pendingCwd;
1406
1420
  pendingPrompt = void 0;
1407
1421
  pendingSession = void 0;
1422
+ pendingCwd = void 0;
1423
+ const rawCwd = process.cwd();
1424
+ const currentSession = basename3(findMatchingDirectory(rawCwd, directories) ?? rawCwd);
1408
1425
  return render(
1409
1426
  createElement(ManagerView, {
1410
1427
  actions,
1411
- currentSession: basename3(process.cwd()),
1412
- currentCwd: process.cwd(),
1428
+ currentSession,
1413
1429
  directories,
1414
1430
  restoredPrompt: prompt,
1415
- restoredSession: session
1431
+ restoredSession: session,
1432
+ restoredCwd: cwd
1416
1433
  }),
1417
1434
  { concurrent: true }
1418
1435
  );
@@ -1470,10 +1487,11 @@ var createListCommand = ({ usecases }) => async () => {
1470
1487
  console.log("No sessions found.");
1471
1488
  return;
1472
1489
  }
1473
- for (const group of result.sessionGroups) {
1490
+ const lines = result.sessionGroups.map((group) => {
1474
1491
  const paneCount = group.tabs.reduce((sum, t) => sum + t.panes.length, 0);
1475
- console.log(`${group.sessionName} (${String(paneCount)} panes)`);
1476
- }
1492
+ return `${group.sessionName} (${String(paneCount)} panes)`;
1493
+ });
1494
+ console.log(lines.join("\n"));
1477
1495
  };
1478
1496
 
1479
1497
  // src/cli/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmux",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cut0/abmux.git"