abmux 0.0.4 → 0.0.6

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 +667 -312
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -144,10 +144,97 @@ var createEditor = () => ({
144
144
  }
145
145
  });
146
146
 
147
+ // src/infra/claude-cli.ts
148
+ import { execFile as execFile2, execFileSync as execFileSync2 } from "node:child_process";
149
+
150
+ // src/models/claude-session.ts
151
+ var SESSION_SUMMARY_SCHEMA = {
152
+ type: "object",
153
+ properties: {
154
+ overallSummary: { type: "string" },
155
+ sessions: {
156
+ type: "array",
157
+ items: {
158
+ type: "object",
159
+ properties: {
160
+ sessionName: { type: "string" },
161
+ panes: {
162
+ type: "array",
163
+ items: {
164
+ type: "object",
165
+ properties: {
166
+ paneTitle: { type: "string" },
167
+ description: { type: "string" }
168
+ },
169
+ required: ["paneTitle", "description"]
170
+ }
171
+ }
172
+ },
173
+ required: ["sessionName", "panes"]
174
+ }
175
+ }
176
+ },
177
+ required: ["overallSummary", "sessions"]
178
+ };
179
+
180
+ // src/infra/claude-cli.ts
181
+ var resolveClaudePath = () => {
182
+ try {
183
+ return execFileSync2("which", ["claude"], { encoding: "utf-8" }).trim();
184
+ } catch {
185
+ return "claude";
186
+ }
187
+ };
188
+ var claudePath = resolveClaudePath();
189
+ var EMPTY_RESULT = { overallSummary: "", sessions: [] };
190
+ var execClaude = (args) => new Promise((resolve, reject) => {
191
+ execFile2(claudePath, args, { timeout: 12e4 }, (error, stdout, stderr) => {
192
+ if (error) {
193
+ reject(new Error(`claude failed: ${stderr || error.message}`));
194
+ return;
195
+ }
196
+ resolve(stdout.trim());
197
+ });
198
+ });
199
+ var parseResult = (raw) => {
200
+ const output = JSON.parse(raw);
201
+ if (output.is_error) return EMPTY_RESULT;
202
+ const parsed = output.structured_output;
203
+ if (typeof parsed !== "object" || parsed === null || !("sessions" in parsed) || !Array.isArray(parsed.sessions)) {
204
+ return EMPTY_RESULT;
205
+ }
206
+ const result = parsed;
207
+ return {
208
+ overallSummary: result.overallSummary ?? "",
209
+ sessions: result.sessions
210
+ };
211
+ };
212
+ var createClaudeCli = () => ({
213
+ querySessionSummary: async (prompt) => {
214
+ try {
215
+ const raw = await execClaude([
216
+ "-p",
217
+ "--output-format",
218
+ "json",
219
+ "--json-schema",
220
+ JSON.stringify(SESSION_SUMMARY_SCHEMA),
221
+ "--no-session-persistence",
222
+ "--model",
223
+ "haiku",
224
+ prompt
225
+ ]);
226
+ return parseResult(raw);
227
+ } catch {
228
+ return EMPTY_RESULT;
229
+ }
230
+ }
231
+ });
232
+
147
233
  // src/infra/index.ts
148
234
  var createInfra = () => ({
149
235
  tmuxCli: createTmuxCli(),
150
- editor: createEditor()
236
+ editor: createEditor(),
237
+ claudeCli: createClaudeCli()
151
238
  });
152
239
 
153
240
  // src/models/session.ts
@@ -178,15 +265,6 @@ var PANE_KIND = {
178
265
  busy: "busy"
179
266
  };
180
267
 
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
268
  // src/services/session-detection-service.ts
191
269
  var BUSY_TITLES = /* @__PURE__ */ new Set([
192
270
  "nvim",
@@ -252,66 +330,44 @@ var toUnifiedPane = (pane) => {
252
330
  var createSessionDetectionService = () => ({
253
331
  groupBySession: ({ panes }) => {
254
332
  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
- });
333
+ const panesByWindow = Map.groupBy(panes, windowKey);
334
+ const sortPanes = (items) => items.toSorted((a, b) => {
335
+ const kindOrder = { claude: 0, available: 1, busy: 2 };
336
+ const kindDiff = kindOrder[a.kind] - kindOrder[b.kind];
337
+ if (kindDiff !== 0) return kindDiff;
338
+ if (a.kind === "claude" && b.kind === "claude") {
339
+ const statusOrder = {
340
+ "waiting-confirm": 0,
341
+ "waiting-input": 1,
342
+ thinking: 2,
343
+ "tool-running": 3,
344
+ idle: 4
345
+ };
346
+ const sa = statusOrder[a.claudeStatus ?? "idle"] ?? 4;
347
+ const sb = statusOrder[b.claudeStatus ?? "idle"] ?? 4;
348
+ if (sa !== sb) return sa - sb;
274
349
  }
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
- })
350
+ return a.pane.paneIndex - b.pane.paneIndex;
351
+ });
352
+ const windowGroups = [...panesByWindow.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([, group]) => {
353
+ const first = group[0];
354
+ const activePaneTitle = group.find((p) => p.isActive)?.title ?? "";
355
+ return {
356
+ windowIndex: first.windowIndex,
357
+ windowName: first.windowName || activePaneTitle || `Window ${String(first.windowIndex)}`,
358
+ sessionName: first.sessionName,
359
+ panes: sortPanes(group.map(toUnifiedPane))
360
+ };
361
+ });
362
+ const sessionGroups = Map.groupBy(windowGroups, (win) => win.sessionName);
363
+ return [...sessionGroups.entries()].map(([sessionName, wins]) => ({
364
+ sessionName,
365
+ tabs: wins.map(({ windowIndex, windowName, panes: p }) => ({
366
+ windowIndex,
367
+ windowName,
368
+ panes: p
369
+ }))
298
370
  }));
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
371
  },
316
372
  detectStatusFromText
317
373
  });
@@ -396,24 +452,15 @@ var findProjects = async (dir, depth) => {
396
452
  const dirs = entries.filter(
397
453
  (e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP_DIRS.has(e.name)
398
454
  );
399
- const results = [];
400
- const childScans = [];
401
- for (const entry of dirs) {
455
+ const childScans = dirs.map((entry) => {
402
456
  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
- }
457
+ return exists(join2(fullPath, ".git")).then(async (isProject) => {
458
+ if (isProject) return [fullPath];
459
+ return await findProjects(fullPath, depth + 1);
460
+ });
461
+ });
410
462
  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;
463
+ return nested.flat();
417
464
  } catch {
418
465
  return [];
419
466
  }
@@ -427,11 +474,51 @@ var createDirectoryScanService = () => ({
427
474
  }
428
475
  });
429
476
 
477
+ // src/services/session-summary-service.ts
478
+ var EMPTY_RESULT2 = { overallSummary: "", sessions: [] };
479
+ var formatGroupsForPrompt = (groups) => {
480
+ const lines = [];
481
+ for (const group of groups) {
482
+ const panes = group.tabs.flatMap((t) => t.panes);
483
+ const paneDescriptions = panes.map((p) => {
484
+ if (p.kind === "claude") {
485
+ const status = p.claudeStatus ? SESSION_STATUS_LABEL[p.claudeStatus] : "idle";
486
+ const title = p.claudeTitle ?? "";
487
+ return ` - [claude] status=${status} title="${title}"`;
488
+ }
489
+ return ` - [${p.kind}] title="${p.pane.title}"`;
490
+ });
491
+ lines.push(`session: ${group.sessionName}`);
492
+ lines.push(` panes (${String(panes.length)}):`);
493
+ lines.push(...paneDescriptions);
494
+ }
495
+ return lines.join("\n");
496
+ };
497
+ var buildPrompt = (groups) => {
498
+ const data = formatGroupsForPrompt(groups);
499
+ return [
500
+ "\u4EE5\u4E0B\u306F tmux \u30BB\u30C3\u30B7\u30E7\u30F3\u3068\u30DA\u30A4\u30F3\u306E\u4E00\u89A7\u3067\u3059\u3002",
501
+ "overallSummary: \u5168\u4F53\u3068\u3057\u3066\u4ECA\u3069\u3046\u3044\u3046\u4F5C\u696D\u304C\u884C\u308F\u308C\u3066\u3044\u3066\u3001\u3069\u3046\u3044\u3046\u72B6\u614B\u304B\u3092\u65E5\u672C\u8A9E\u3067\u7C21\u6F54\u306B1\u301C2\u6587\u3067\u8AAC\u660E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
502
+ "sessions.panes: \u5404\u30DA\u30A4\u30F3\u304C\u4F55\u3092\u3057\u3066\u3044\u308B\u304B\u3001\u65E5\u672C\u8A9E\u3067\u7C21\u6F54\u306B1\u6587\u305A\u3064\u8AAC\u660E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
503
+ "sessionName \u306F\u305D\u306E\u307E\u307E\u3001paneTitle \u306F\u30DA\u30A4\u30F3\u306E title \u3092\u305D\u306E\u307E\u307E\u5165\u308C\u3066\u304F\u3060\u3055\u3044\u3002",
504
+ "",
505
+ data
506
+ ].join("\n");
507
+ };
508
+ var createSessionSummaryService = (context) => ({
509
+ fetchSummary: async (groups) => {
510
+ if (groups.length === 0) return EMPTY_RESULT2;
511
+ const prompt = buildPrompt(groups);
512
+ return await context.infra.claudeCli.querySessionSummary(prompt);
513
+ }
514
+ });
515
+
430
516
  // src/services/index.ts
431
517
  var createServices = (context) => ({
432
518
  tmux: createTmuxService(context),
433
519
  sessionDetection: createSessionDetectionService(),
434
- directoryScan: createDirectoryScanService()
520
+ directoryScan: createDirectoryScanService(),
521
+ sessionSummary: createSessionSummaryService(context)
435
522
  });
436
523
 
437
524
  // src/utils/ShellUtils.ts
@@ -464,6 +551,9 @@ var createManagerUsecase = (context) => {
464
551
  const sessionGroups = sessionDetection.groupBySession({ panes });
465
552
  return { sessionGroups };
466
553
  },
554
+ fetchOverview: async (groups) => {
555
+ return await context.services.sessionSummary.fetchSummary(groups);
556
+ },
467
557
  enrichStatus: async (up) => {
468
558
  if (up.kind !== "claude") return up;
469
559
  try {
@@ -507,7 +597,7 @@ var createUsecases = (context) => ({
507
597
  // package.json
508
598
  var package_default = {
509
599
  name: "abmux",
510
- version: "0.0.4",
600
+ version: "0.0.6",
511
601
  repository: {
512
602
  type: "git",
513
603
  url: "https://github.com/cut0/abmux.git"
@@ -577,8 +667,8 @@ import { createElement } from "react";
577
667
 
578
668
  // src/components/ManagerView.tsx
579
669
  import { basename as basename2 } from "node:path";
580
- import { Box as Box10, Text as Text10 } from "ink";
581
- import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo5, useState as useState5 } from "react";
670
+ import { Box as Box11 } from "ink";
671
+ import { useCallback as useCallback4, useMemo as useMemo7, useRef as useRef3, useState as useState6 } from "react";
582
672
 
583
673
  // src/components/shared/Header.tsx
584
674
  import { Box, Text } from "ink";
@@ -589,24 +679,57 @@ var Header = ({ title }) => {
589
679
 
590
680
  // src/components/shared/StatusBar.tsx
591
681
  import { Box as Box2, Text as Text2 } from "ink";
592
- import { jsx as jsx2 } from "react/jsx-runtime";
682
+ import { useMemo } from "react";
683
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
684
+ var STATUS_ICON = {
685
+ [SESSION_STATUS.waitingInput]: "\u276F",
686
+ [SESSION_STATUS.waitingConfirm]: "\u2753",
687
+ [SESSION_STATUS.thinking]: "\u25CF",
688
+ [SESSION_STATUS.toolRunning]: "\u2733",
689
+ [SESSION_STATUS.idle]: "\u25CB"
690
+ };
593
691
  var COLOR_MAP = {
594
692
  success: "green",
595
693
  error: "red",
596
694
  info: "gray"
597
695
  };
598
- var StatusBar = ({ message, type = "info" }) => {
599
- return /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: COLOR_MAP[type], children: message }) });
696
+ var STATUS_ORDER = [
697
+ SESSION_STATUS.thinking,
698
+ SESSION_STATUS.toolRunning,
699
+ SESSION_STATUS.waitingConfirm,
700
+ SESSION_STATUS.waitingInput,
701
+ SESSION_STATUS.idle
702
+ ];
703
+ var StatusBar = ({ message, type = "info", statusCounts }) => {
704
+ const summaryEntries = useMemo(() => {
705
+ if (!statusCounts) return [];
706
+ return STATUS_ORDER.filter((s) => (statusCounts[s] ?? 0) > 0).map((s) => ({
707
+ status: s,
708
+ icon: STATUS_ICON[s],
709
+ label: SESSION_STATUS_LABEL[s],
710
+ color: SESSION_STATUS_COLOR[s],
711
+ count: statusCounts[s] ?? 0
712
+ }));
713
+ }, [statusCounts]);
714
+ return /* @__PURE__ */ jsxs(Box2, { children: [
715
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, children: /* @__PURE__ */ jsx2(Text2, { color: COLOR_MAP[type], children: message }) }),
716
+ summaryEntries.length > 0 && /* @__PURE__ */ jsx2(Box2, { gap: 1, children: summaryEntries.map((entry) => /* @__PURE__ */ jsxs(Text2, { color: entry.color, children: [
717
+ entry.icon,
718
+ String(entry.count),
719
+ " ",
720
+ entry.label
721
+ ] }, entry.status)) })
722
+ ] });
600
723
  };
601
724
 
602
725
  // src/components/shared/DirectorySelect.tsx
603
726
  import { Box as Box3, Text as Text3, useApp, useInput } from "ink";
604
- import { useCallback, useMemo as useMemo2, useState } from "react";
727
+ import { useCallback, useMemo as useMemo3, useState } from "react";
605
728
 
606
729
  // src/hooks/use-scroll.ts
607
- import { useMemo } from "react";
730
+ import { useMemo as useMemo2 } from "react";
608
731
  var useScroll = (cursor, totalItems, availableRows) => {
609
- return useMemo(() => {
732
+ return useMemo2(() => {
610
733
  const visibleCount = Math.max(1, availableRows);
611
734
  if (totalItems <= visibleCount) {
612
735
  return { scrollOffset: 0, visibleCount };
@@ -618,17 +741,29 @@ var useScroll = (cursor, totalItems, availableRows) => {
618
741
  }, [cursor, totalItems, availableRows]);
619
742
  };
620
743
 
744
+ // src/utils/PathUtils.ts
745
+ var formatCwd = (cwd) => {
746
+ const home = process.env["HOME"] ?? "";
747
+ if (home && cwd.startsWith(home)) {
748
+ return `~${cwd.slice(home.length)}`;
749
+ }
750
+ return cwd;
751
+ };
752
+ var findMatchingDirectory = (path, directories) => directories.filter((dir) => path === dir || path.startsWith(dir + "/")).reduce(
753
+ (best, dir) => !best || dir.length > best.length ? dir : best,
754
+ void 0
755
+ );
756
+
621
757
  // src/components/shared/DirectorySelect.tsx
622
- 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);
758
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
759
+ var sortSessions = (sessions, currentSession) => {
760
+ const current = sessions.filter((s) => s.name === currentSession);
761
+ const rest = sessions.filter((s) => s.name !== currentSession);
626
762
  return [...current, ...rest];
627
763
  };
628
764
  var SessionListPanel = ({
629
- sessionGroups,
765
+ sessions,
630
766
  currentSession,
631
- sessionPathMap,
632
767
  isFocused,
633
768
  availableRows,
634
769
  onSelect,
@@ -638,30 +773,30 @@ var SessionListPanel = ({
638
773
  }) => {
639
774
  const { exit } = useApp();
640
775
  const [cursor, setCursor] = useState(0);
641
- const sortedGroups = useMemo2(
642
- () => sortSessionGroups(sessionGroups, currentSession),
643
- [sessionGroups, currentSession]
776
+ const sortedSessions = useMemo3(
777
+ () => sortSessions(sessions, currentSession),
778
+ [sessions, currentSession]
644
779
  );
645
- const sessions = useMemo2(() => sortedGroups.map((g) => g.sessionName), [sortedGroups]);
646
- const clampedCursor = cursor >= sessions.length ? Math.max(0, sessions.length - 1) : cursor;
780
+ const names = useMemo3(() => sortedSessions.map((s) => s.name), [sortedSessions]);
781
+ const clampedCursor = cursor >= names.length ? Math.max(0, names.length - 1) : cursor;
647
782
  if (clampedCursor !== cursor) {
648
783
  setCursor(clampedCursor);
649
784
  }
650
785
  const reservedLines = 1;
651
786
  const { scrollOffset, visibleCount } = useScroll(
652
787
  clampedCursor,
653
- sessions.length,
788
+ names.length,
654
789
  availableRows - reservedLines
655
790
  );
656
- const visibleSessions = sessions.slice(scrollOffset, scrollOffset + visibleCount);
791
+ const visibleSessions = sortedSessions.slice(scrollOffset, scrollOffset + visibleCount);
657
792
  const moveCursor = useCallback(
658
793
  (next) => {
659
- const clamped = Math.max(0, Math.min(sessions.length - 1, next));
794
+ const clamped = Math.max(0, Math.min(names.length - 1, next));
660
795
  setCursor(clamped);
661
- const name = sessions[clamped];
796
+ const name = names[clamped];
662
797
  if (name) onCursorChange(name);
663
798
  },
664
- [sessions, onCursorChange]
799
+ [names, onCursorChange]
665
800
  );
666
801
  useInput(
667
802
  (input, key) => {
@@ -678,12 +813,12 @@ var SessionListPanel = ({
678
813
  return;
679
814
  }
680
815
  if (key.return || key.rightArrow) {
681
- const name = sessions[clampedCursor];
816
+ const name = names[clampedCursor];
682
817
  if (name) onSelect(name);
683
818
  return;
684
819
  }
685
820
  if (input === "d" && onDeleteSession) {
686
- const name = sessions[clampedCursor];
821
+ const name = names[clampedCursor];
687
822
  if (name) onDeleteSession(name);
688
823
  return;
689
824
  }
@@ -693,28 +828,27 @@ var SessionListPanel = ({
693
828
  },
694
829
  { isActive: isFocused }
695
830
  );
696
- return /* @__PURE__ */ jsxs(Box3, { flexDirection: "column", children: [
697
- /* @__PURE__ */ jsxs(Box3, { paddingLeft: 1, children: [
831
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
832
+ /* @__PURE__ */ jsxs2(Box3, { paddingLeft: 1, children: [
698
833
  /* @__PURE__ */ jsx3(Text3, { bold: true, color: isFocused ? "green" : "gray", children: "Sessions" }),
699
- /* @__PURE__ */ jsxs(Text3, { dimColor: true, children: [
834
+ /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
700
835
  " ",
701
836
  "(",
702
837
  clampedCursor + 1,
703
838
  "/",
704
- sessions.length,
839
+ names.length,
705
840
  ")"
706
841
  ] })
707
842
  ] }),
708
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleSessions.map((name, i) => {
843
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleSessions.map((session, i) => {
709
844
  const globalIndex = scrollOffset + i;
710
845
  const isHighlighted = globalIndex === clampedCursor;
711
- const isCurrent = name === currentSession;
712
- const path = sessionPathMap.get(name);
713
- return /* @__PURE__ */ jsxs(Box3, { paddingLeft: 1, gap: 1, children: [
846
+ const isCurrent = session.name === currentSession;
847
+ return /* @__PURE__ */ jsxs2(Box3, { paddingLeft: 1, gap: 1, children: [
714
848
  /* @__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 }),
849
+ /* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : "cyan", bold: isHighlighted, wrap: "truncate", children: session.path ? formatCwd(session.path) : session.name }),
716
850
  isCurrent && /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "(cwd)" })
717
- ] }, name);
851
+ ] }, session.name);
718
852
  }) })
719
853
  ] });
720
854
  };
@@ -724,21 +858,21 @@ import { Box as Box6, Text as Text6 } from "ink";
724
858
 
725
859
  // src/components/PaneListView.tsx
726
860
  import { Box as Box5, Text as Text5, useApp as useApp2, useInput as useInput2 } from "ink";
727
- import { useCallback as useCallback2, useMemo as useMemo3, useRef, useState as useState2 } from "react";
861
+ import { useCallback as useCallback2, useMemo as useMemo4, useRef, useState as useState2 } from "react";
728
862
 
729
863
  // src/components/sessions/PaneItem.tsx
730
864
  import { Box as Box4, Text as Text4 } from "ink";
731
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
865
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
732
866
  var PaneItem = ({ unifiedPane, isHighlighted }) => {
733
867
  const { pane, kind, claudeStatus, claudeTitle } = unifiedPane;
734
868
  if (kind === "claude") {
735
869
  const icon = pane.title.charAt(0);
736
870
  const statusLabel = claudeStatus ? SESSION_STATUS_LABEL[claudeStatus] : "";
737
871
  const statusColor = claudeStatus ? SESSION_STATUS_COLOR[claudeStatus] : "gray";
738
- return /* @__PURE__ */ jsxs2(Box4, { paddingLeft: 3, gap: 1, children: [
872
+ return /* @__PURE__ */ jsxs3(Box4, { paddingLeft: 3, gap: 1, children: [
739
873
  /* @__PURE__ */ jsx4(Text4, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
740
874
  /* @__PURE__ */ jsx4(Text4, { color: "#FF8C00", children: icon }),
741
- /* @__PURE__ */ jsxs2(Text4, { color: statusColor, children: [
875
+ /* @__PURE__ */ jsxs3(Text4, { color: statusColor, children: [
742
876
  "[",
743
877
  statusLabel,
744
878
  "]"
@@ -747,7 +881,7 @@ var PaneItem = ({ unifiedPane, isHighlighted }) => {
747
881
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: pane.paneId })
748
882
  ] });
749
883
  }
750
- return /* @__PURE__ */ jsxs2(Box4, { paddingLeft: 3, gap: 1, children: [
884
+ return /* @__PURE__ */ jsxs3(Box4, { paddingLeft: 3, gap: 1, children: [
751
885
  /* @__PURE__ */ jsx4(Text4, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
752
886
  kind === "available" && /* @__PURE__ */ jsx4(Text4, { color: "#4AA8D8", children: "\u25CB" }),
753
887
  kind === "busy" && /* @__PURE__ */ jsx4(Text4, { color: "#E05252", children: "\u25CF" }),
@@ -757,7 +891,7 @@ var PaneItem = ({ unifiedPane, isHighlighted }) => {
757
891
  };
758
892
 
759
893
  // src/components/PaneListView.tsx
760
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
894
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
761
895
  var PaneListView = ({
762
896
  selectedSession,
763
897
  group,
@@ -773,7 +907,7 @@ var PaneListView = ({
773
907
  const { exit } = useApp2();
774
908
  const [cursor, setCursor] = useState2(0);
775
909
  const highlightedRef = useRef(void 0);
776
- const panes = useMemo3(() => group.tabs.flatMap((t) => t.panes), [group]);
910
+ const panes = useMemo4(() => group.tabs.flatMap((t) => t.panes), [group]);
777
911
  const clampedCursor = cursor >= panes.length ? Math.max(0, panes.length - 1) : cursor;
778
912
  if (clampedCursor !== cursor) {
779
913
  setCursor(clampedCursor);
@@ -784,7 +918,7 @@ var PaneListView = ({
784
918
  panes.length,
785
919
  availableRows - reservedLines
786
920
  );
787
- const visiblePanes = useMemo3(
921
+ const visiblePanes = useMemo4(
788
922
  () => panes.slice(scrollOffset, scrollOffset + visibleCount),
789
923
  [panes, scrollOffset, visibleCount]
790
924
  );
@@ -853,10 +987,10 @@ var PaneListView = ({
853
987
  },
854
988
  { isActive: isFocused }
855
989
  );
856
- return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", children: [
857
- /* @__PURE__ */ jsxs3(Box5, { paddingLeft: 1, children: [
990
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
991
+ /* @__PURE__ */ jsxs4(Box5, { paddingLeft: 1, children: [
858
992
  /* @__PURE__ */ jsx5(Text5, { bold: true, color: isFocused ? "green" : "gray", children: "Panes" }),
859
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
993
+ /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
860
994
  " ",
861
995
  selectedSession,
862
996
  " (",
@@ -912,15 +1046,140 @@ var PaneListPanel = ({
912
1046
  );
913
1047
  };
914
1048
 
1049
+ // src/components/SessionOverviewPanel.tsx
1050
+ import { Spinner } from "@inkjs/ui";
1051
+ import { Box as Box7, Text as Text7, useApp as useApp3, useInput as useInput3 } from "ink";
1052
+ import { useCallback as useCallback3, useMemo as useMemo5, useState as useState3 } from "react";
1053
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1054
+ var SessionOverviewPanel = ({
1055
+ overallSummary,
1056
+ items,
1057
+ groups,
1058
+ isLoading,
1059
+ isFocused,
1060
+ availableRows,
1061
+ onBack
1062
+ }) => {
1063
+ const { exit } = useApp3();
1064
+ const [cursor, setCursor] = useState3(0);
1065
+ const lines = useMemo5(() => {
1066
+ const summaryLines = overallSummary ? [
1067
+ { key: "summary", type: "summary", text: overallSummary },
1068
+ { key: "spacer:summary", type: "spacer" }
1069
+ ] : [];
1070
+ const sessionLines = items.flatMap((item, idx) => {
1071
+ const group = groups.find((g) => g.sessionName === item.sessionName);
1072
+ const allPanes = group?.tabs.flatMap((t) => t.panes) ?? [];
1073
+ const paneLines = item.panes.map((paneSummary) => {
1074
+ const matched = allPanes.find(
1075
+ (p) => (p.claudeTitle ?? p.pane.title) === paneSummary.paneTitle
1076
+ );
1077
+ const statusLabel = matched?.claudeStatus ? SESSION_STATUS_LABEL[matched.claudeStatus] : void 0;
1078
+ const statusColor = matched?.claudeStatus ? SESSION_STATUS_COLOR[matched.claudeStatus] : void 0;
1079
+ return {
1080
+ key: `p:${item.sessionName}:${paneSummary.paneTitle}`,
1081
+ type: "pane",
1082
+ statusLabel,
1083
+ statusColor,
1084
+ description: paneSummary.description
1085
+ };
1086
+ });
1087
+ const spacer = idx > 0 ? [{ key: `spacer:${item.sessionName}`, type: "spacer" }] : [];
1088
+ return [
1089
+ ...spacer,
1090
+ { key: `s:${item.sessionName}`, type: "session", sessionName: item.sessionName },
1091
+ ...paneLines
1092
+ ];
1093
+ });
1094
+ return [...summaryLines, ...sessionLines];
1095
+ }, [overallSummary, items, groups]);
1096
+ const clampedCursor = cursor >= lines.length ? Math.max(0, lines.length - 1) : cursor;
1097
+ if (clampedCursor !== cursor) {
1098
+ setCursor(clampedCursor);
1099
+ }
1100
+ const reservedLines = 3;
1101
+ const { scrollOffset, visibleCount } = useScroll(
1102
+ clampedCursor,
1103
+ lines.length,
1104
+ availableRows - reservedLines
1105
+ );
1106
+ const visibleLines = useMemo5(
1107
+ () => lines.slice(scrollOffset, scrollOffset + visibleCount),
1108
+ [lines, scrollOffset, visibleCount]
1109
+ );
1110
+ const moveCursor = useCallback3(
1111
+ (next) => {
1112
+ setCursor(Math.max(0, Math.min(lines.length - 1, next)));
1113
+ },
1114
+ [lines.length]
1115
+ );
1116
+ useInput3(
1117
+ (input, key) => {
1118
+ if (input === "q") {
1119
+ exit();
1120
+ return;
1121
+ }
1122
+ if (key.escape || key.leftArrow) {
1123
+ onBack();
1124
+ return;
1125
+ }
1126
+ if (key.upArrow) {
1127
+ moveCursor(clampedCursor - 1);
1128
+ return;
1129
+ }
1130
+ if (key.downArrow) {
1131
+ moveCursor(clampedCursor + 1);
1132
+ }
1133
+ },
1134
+ { isActive: isFocused }
1135
+ );
1136
+ return /* @__PURE__ */ jsxs5(
1137
+ Box7,
1138
+ {
1139
+ flexDirection: "column",
1140
+ height: availableRows,
1141
+ borderStyle: "round",
1142
+ borderColor: isFocused ? "green" : "gray",
1143
+ children: [
1144
+ /* @__PURE__ */ jsxs5(Box7, { paddingLeft: 1, gap: 1, children: [
1145
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: isFocused ? "green" : "gray", children: "Overview" }),
1146
+ isLoading && /* @__PURE__ */ jsx7(Spinner, { label: "" })
1147
+ ] }),
1148
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: lines.length === 0 && !isLoading ? /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "No sessions." }) }) : visibleLines.map((line, i) => {
1149
+ const globalIndex = scrollOffset + i;
1150
+ const isHighlighted = isFocused && globalIndex === clampedCursor;
1151
+ if (line.type === "spacer") {
1152
+ return /* @__PURE__ */ jsx7(Box7, { height: 1 }, line.key);
1153
+ }
1154
+ if (line.type === "summary") {
1155
+ return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text7, { color: isHighlighted ? "green" : void 0, wrap: "wrap", children: line.text }) }, line.key);
1156
+ }
1157
+ if (line.type === "session") {
1158
+ return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text7, { color: isHighlighted ? "green" : "cyan", children: line.sessionName }) }, line.key);
1159
+ }
1160
+ return /* @__PURE__ */ jsxs5(Box7, { paddingLeft: 3, gap: 1, children: [
1161
+ line.statusLabel ? /* @__PURE__ */ jsxs5(Text7, { color: isHighlighted ? "green" : line.statusColor, children: [
1162
+ "[",
1163
+ line.statusLabel,
1164
+ "]"
1165
+ ] }) : /* @__PURE__ */ jsx7(Text7, { color: isHighlighted ? "green" : "gray", children: "[--]" }),
1166
+ /* @__PURE__ */ jsx7(Text7, { color: isHighlighted ? "green" : void 0, wrap: "wrap", children: line.description })
1167
+ ] }, line.key);
1168
+ }) })
1169
+ ]
1170
+ }
1171
+ );
1172
+ };
1173
+
915
1174
  // src/components/ConfirmView.tsx
916
- import { Box as Box7, Text as Text7, useInput as useInput3 } from "ink";
1175
+ import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
917
1176
 
918
1177
  // src/hooks/use-terminal-size.ts
919
1178
  import { useStdout } from "ink";
920
- import { useEffect, useState as useState3 } from "react";
1179
+ import { useEffect, useState as useState4 } from "react";
921
1180
  var useTerminalSize = () => {
922
1181
  const { stdout } = useStdout();
923
- const [size, setSize] = useState3({
1182
+ const [size, setSize] = useState4({
924
1183
  rows: stdout.rows ?? 24,
925
1184
  columns: stdout.columns ?? 80
926
1185
  });
@@ -940,12 +1199,12 @@ var useTerminalSize = () => {
940
1199
  };
941
1200
 
942
1201
  // src/components/ConfirmView.tsx
943
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1202
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
944
1203
  var ConfirmView = ({ selectedDir, prompt, onConfirm, onCancel }) => {
945
1204
  const { rows } = useTerminalSize();
946
1205
  const previewLines = prompt.split("\n");
947
1206
  const maxPreview = Math.min(previewLines.length, rows - 6);
948
- useInput3((_input, key) => {
1207
+ useInput4((_input, key) => {
949
1208
  if (key.return) {
950
1209
  onConfirm();
951
1210
  return;
@@ -954,30 +1213,30 @@ var ConfirmView = ({ selectedDir, prompt, onConfirm, onCancel }) => {
954
1213
  onCancel();
955
1214
  }
956
1215
  });
957
- return /* @__PURE__ */ jsxs4(Box7, { flexDirection: "column", height: rows, children: [
958
- /* @__PURE__ */ jsx7(Header, { title: `${APP_TITLE} \u2014 ${selectedDir}` }),
959
- /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { bold: true, children: "New Claude session:" }) }),
960
- /* @__PURE__ */ jsxs4(Box7, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingLeft: 2, children: [
961
- previewLines.slice(0, maxPreview).map((line, i) => /* @__PURE__ */ jsx7(Text7, { color: "white", children: line }, i)),
962
- previewLines.length > maxPreview && /* @__PURE__ */ jsxs4(Text7, { dimColor: true, children: [
1216
+ return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", height: rows, children: [
1217
+ /* @__PURE__ */ jsx8(Header, { title: `${APP_TITLE} \u2014 ${selectedDir}` }),
1218
+ /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { bold: true, children: "New Claude session:" }) }),
1219
+ /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingLeft: 2, children: [
1220
+ previewLines.slice(0, maxPreview).map((line, i) => /* @__PURE__ */ jsx8(Text8, { color: "white", children: line }, i)),
1221
+ previewLines.length > maxPreview && /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
963
1222
  "... (",
964
1223
  previewLines.length - maxPreview,
965
1224
  " more lines)"
966
1225
  ] })
967
1226
  ] }),
968
- /* @__PURE__ */ jsxs4(Box7, { gap: 2, children: [
969
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Enter confirm" }),
970
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Esc cancel" })
1227
+ /* @__PURE__ */ jsxs6(Box8, { gap: 2, children: [
1228
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Enter confirm" }),
1229
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Esc cancel" })
971
1230
  ] })
972
1231
  ] });
973
1232
  };
974
1233
 
975
1234
  // src/components/DeleteSessionView.tsx
976
- import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
977
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1235
+ import { Box as Box9, Text as Text9, useInput as useInput5 } from "ink";
1236
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
978
1237
  var DeleteSessionView = ({ sessionName, paneCount, onConfirm, onCancel }) => {
979
1238
  const { rows } = useTerminalSize();
980
- useInput4((_input, key) => {
1239
+ useInput5((_input, key) => {
981
1240
  if (key.return) {
982
1241
  onConfirm();
983
1242
  return;
@@ -986,33 +1245,33 @@ var DeleteSessionView = ({ sessionName, paneCount, onConfirm, onCancel }) => {
986
1245
  onCancel();
987
1246
  }
988
1247
  });
989
- return /* @__PURE__ */ jsxs5(Box8, { flexDirection: "column", height: rows, children: [
990
- /* @__PURE__ */ jsx8(Header, { title: APP_TITLE }),
991
- /* @__PURE__ */ jsxs5(Box8, { flexDirection: "column", gap: 1, paddingLeft: 2, children: [
992
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: "red", children: "Delete session?" }),
993
- /* @__PURE__ */ jsxs5(Text8, { children: [
1248
+ return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", height: rows, children: [
1249
+ /* @__PURE__ */ jsx9(Header, { title: APP_TITLE }),
1250
+ /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", gap: 1, paddingLeft: 2, children: [
1251
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "red", children: "Delete session?" }),
1252
+ /* @__PURE__ */ jsxs7(Text9, { children: [
994
1253
  "Session: ",
995
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: sessionName })
1254
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: sessionName })
996
1255
  ] }),
997
- /* @__PURE__ */ jsxs5(Text8, { children: [
1256
+ /* @__PURE__ */ jsxs7(Text9, { children: [
998
1257
  "Panes: ",
999
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: paneCount })
1258
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: paneCount })
1000
1259
  ] }),
1001
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "All processes in this session will be terminated." })
1260
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "All processes in this session will be terminated." })
1002
1261
  ] }),
1003
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1 }),
1004
- /* @__PURE__ */ jsxs5(Box8, { gap: 2, children: [
1005
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Enter confirm" }),
1006
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Esc cancel" })
1262
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1 }),
1263
+ /* @__PURE__ */ jsxs7(Box9, { gap: 2, children: [
1264
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter confirm" }),
1265
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Esc cancel" })
1007
1266
  ] })
1008
1267
  ] });
1009
1268
  };
1010
1269
 
1011
1270
  // src/components/DirectorySearchView.tsx
1012
- import { Box as Box9, Text as Text9, useInput as useInput5 } from "ink";
1013
- import { useMemo as useMemo4, useState as useState4 } from "react";
1271
+ import { Box as Box10, Text as Text10, useInput as useInput6 } from "ink";
1272
+ import { useMemo as useMemo6, useState as useState5 } from "react";
1014
1273
  import { basename } from "node:path";
1015
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1274
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1016
1275
  var formatPath = (path) => {
1017
1276
  const home = process.env["HOME"] ?? "";
1018
1277
  if (home && path.startsWith(home)) {
@@ -1022,9 +1281,9 @@ var formatPath = (path) => {
1022
1281
  };
1023
1282
  var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
1024
1283
  const { rows } = useTerminalSize();
1025
- const [query, setQuery] = useState4("");
1026
- const [cursor, setCursor] = useState4(0);
1027
- const filtered = useMemo4(() => {
1284
+ const [query, setQuery] = useState5("");
1285
+ const [cursor, setCursor] = useState5(0);
1286
+ const filtered = useMemo6(() => {
1028
1287
  if (!query) return directories;
1029
1288
  const lower = query.toLowerCase();
1030
1289
  return directories.filter((d) => {
@@ -1039,11 +1298,11 @@ var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
1039
1298
  }
1040
1299
  const listHeight = rows - 6;
1041
1300
  const { scrollOffset, visibleCount } = useScroll(clampedCursor, filtered.length, listHeight);
1042
- const visibleItems = useMemo4(
1301
+ const visibleItems = useMemo6(
1043
1302
  () => filtered.slice(scrollOffset, scrollOffset + visibleCount),
1044
1303
  [filtered, scrollOffset, visibleCount]
1045
1304
  );
1046
- useInput5((input, key) => {
1305
+ useInput6((input, key) => {
1047
1306
  if (key.escape) {
1048
1307
  onCancel();
1049
1308
  return;
@@ -1071,33 +1330,50 @@ var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
1071
1330
  setCursor(0);
1072
1331
  }
1073
1332
  });
1074
- return /* @__PURE__ */ jsxs6(Box9, { flexDirection: "column", height: rows, children: [
1075
- /* @__PURE__ */ jsx9(Header, { title: `${APP_TITLE} \u2014 Add Session` }),
1076
- /* @__PURE__ */ jsxs6(Box9, { paddingLeft: 1, gap: 1, children: [
1077
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: ">" }),
1078
- /* @__PURE__ */ jsx9(Text9, { children: query }),
1079
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: query ? "" : "type to filter..." })
1333
+ return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", height: rows, children: [
1334
+ /* @__PURE__ */ jsx10(Header, { title: `${APP_TITLE} \u2014 Add Session` }),
1335
+ /* @__PURE__ */ jsxs8(Box10, { paddingLeft: 1, gap: 1, children: [
1336
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: ">" }),
1337
+ /* @__PURE__ */ jsx10(Text10, { children: query }),
1338
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: query ? "" : "type to filter..." })
1080
1339
  ] }),
1081
- /* @__PURE__ */ jsx9(Box9, { paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text9, { dimColor: true, children: [
1340
+ /* @__PURE__ */ jsx10(Box10, { paddingLeft: 1, children: /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
1082
1341
  filtered.length,
1083
1342
  "/",
1084
1343
  directories.length
1085
1344
  ] }) }),
1086
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleItems.map((dir, i) => {
1345
+ /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleItems.map((dir, i) => {
1087
1346
  const globalIndex = scrollOffset + i;
1088
1347
  const isHighlighted = globalIndex === clampedCursor;
1089
- return /* @__PURE__ */ jsxs6(Box9, { paddingLeft: 1, gap: 1, children: [
1090
- /* @__PURE__ */ jsx9(Text9, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
1091
- /* @__PURE__ */ jsx9(Text9, { color: isHighlighted ? "green" : void 0, bold: isHighlighted, children: formatPath(dir) })
1348
+ return /* @__PURE__ */ jsxs8(Box10, { paddingLeft: 1, gap: 1, children: [
1349
+ /* @__PURE__ */ jsx10(Text10, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
1350
+ /* @__PURE__ */ jsx10(Text10, { color: isHighlighted ? "green" : void 0, bold: isHighlighted, children: formatPath(dir) })
1092
1351
  ] }, dir);
1093
1352
  }) }),
1094
- /* @__PURE__ */ jsxs6(Box9, { gap: 2, children: [
1095
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter select" }),
1096
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Esc cancel" })
1353
+ /* @__PURE__ */ jsxs8(Box10, { gap: 2, children: [
1354
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Enter select" }),
1355
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Esc cancel" })
1097
1356
  ] })
1098
1357
  ] });
1099
1358
  };
1100
1359
 
1360
+ // src/hooks/use-interval.ts
1361
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
1362
+ var useInterval = (fn, intervalMs, enabled = true) => {
1363
+ const fnRef = useRef2(fn);
1364
+ fnRef.current = fn;
1365
+ useEffect2(() => {
1366
+ if (!enabled) return;
1367
+ fnRef.current();
1368
+ const timer = setInterval(() => {
1369
+ fnRef.current();
1370
+ }, intervalMs);
1371
+ return () => {
1372
+ clearInterval(timer);
1373
+ };
1374
+ }, [intervalMs, enabled]);
1375
+ };
1376
+
1101
1377
  // src/utils/PromiseUtils.ts
1102
1378
  var swallow = async (fn) => {
1103
1379
  try {
@@ -1107,7 +1383,7 @@ var swallow = async (fn) => {
1107
1383
  };
1108
1384
 
1109
1385
  // src/components/ManagerView.tsx
1110
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1386
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1111
1387
  var MODE = {
1112
1388
  split: "split",
1113
1389
  confirm: "confirm",
@@ -1116,165 +1392,199 @@ var MODE = {
1116
1392
  };
1117
1393
  var FOCUS = {
1118
1394
  left: "left",
1119
- right: "right"
1395
+ right: "right",
1396
+ bottom: "bottom"
1120
1397
  };
1121
1398
  var POLL_INTERVAL = 3e3;
1399
+ var OVERVIEW_POLL_INTERVAL = 6e4;
1122
1400
  var ManagerView = ({
1123
1401
  actions,
1124
1402
  currentSession,
1125
- currentCwd,
1126
1403
  directories,
1127
- sessionCwdMap,
1128
1404
  restoredPrompt,
1129
- restoredSession
1405
+ restoredSession,
1406
+ restoredCwd
1130
1407
  }) => {
1131
1408
  const { rows, columns } = useTerminalSize();
1132
- const [fetchState, setFetchState] = useState5({ data: [], isLoading: true });
1133
- const [mode, setMode] = useState5(restoredPrompt ? MODE.confirm : MODE.split);
1134
- const [focus, setFocus] = useState5(FOCUS.left);
1135
- const [selectedSession, setSelectedSession] = useState5(restoredSession);
1136
- const [pendingPrompt, setPendingPrompt] = useState5(restoredPrompt ?? "");
1137
- const [pendingDeleteSession, setPendingDeleteSession] = useState5(void 0);
1138
- const refresh = useCallback3(async () => {
1409
+ const [sessionsState, setSessionsState] = useState6({
1410
+ sessions: [],
1411
+ isLoading: true
1412
+ });
1413
+ const [mode, setMode] = useState6(restoredPrompt ? MODE.confirm : MODE.split);
1414
+ const [focus, setFocus] = useState6(FOCUS.left);
1415
+ const [selectedSession, setSelectedSession] = useState6(restoredSession);
1416
+ const [pendingPrompt, setPendingPrompt] = useState6(restoredPrompt ?? "");
1417
+ const [pendingDeleteSession, setPendingDeleteSession] = useState6(void 0);
1418
+ const [overviewResult, setOverviewResult] = useState6({
1419
+ overallSummary: "",
1420
+ sessions: []
1421
+ });
1422
+ const [overviewLoading, setOverviewLoading] = useState6(true);
1423
+ const overviewInFlightRef = useRef3(false);
1424
+ const refresh = useCallback4(async () => {
1139
1425
  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 });
1426
+ const fetched = await actions.fetchSessions();
1427
+ setSessionsState((prev) => {
1428
+ const fetchedNames = new Set(fetched.map((s) => s.name));
1429
+ const userOnly = prev.sessions.filter(
1430
+ (s) => !fetchedNames.has(s.name) && s.groups.length === 0
1431
+ );
1432
+ return { sessions: [...userOnly, ...fetched], isLoading: false };
1433
+ });
1149
1434
  } catch {
1150
- setFetchState((prev) => ({ ...prev, isLoading: false }));
1435
+ setSessionsState((prev) => ({ ...prev, isLoading: false }));
1151
1436
  }
1152
1437
  }, [actions]);
1153
- useEffect2(() => {
1154
- void refresh();
1155
- const timer = setInterval(() => {
1156
- void refresh();
1157
- }, POLL_INTERVAL);
1158
- return () => {
1159
- clearInterval(timer);
1438
+ useInterval(() => void refresh(), POLL_INTERVAL);
1439
+ useInterval(
1440
+ () => {
1441
+ if (overviewInFlightRef.current) return;
1442
+ overviewInFlightRef.current = true;
1443
+ setOverviewLoading(true);
1444
+ void actions.fetchOverview(sessionsState.sessions).then((result) => {
1445
+ setOverviewResult(result);
1446
+ }).catch(() => {
1447
+ }).finally(() => {
1448
+ setOverviewLoading(false);
1449
+ overviewInFlightRef.current = false;
1450
+ });
1451
+ },
1452
+ OVERVIEW_POLL_INTERVAL,
1453
+ !sessionsState.isLoading
1454
+ );
1455
+ const resolvedSession = selectedSession ?? sessionsState.sessions[0]?.name;
1456
+ const selectedManagedSession = useMemo7(
1457
+ () => sessionsState.sessions.find((s) => s.name === resolvedSession),
1458
+ [sessionsState.sessions, resolvedSession]
1459
+ );
1460
+ const selectedGroup = useMemo7(() => {
1461
+ if (!selectedManagedSession) return void 0;
1462
+ return {
1463
+ sessionName: selectedManagedSession.name,
1464
+ tabs: selectedManagedSession.groups.flatMap((g) => g.tabs)
1160
1465
  };
1161
- }, [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]
1466
+ }, [selectedManagedSession]);
1467
+ const allGroups = useMemo7(
1468
+ () => sessionsState.sessions.flatMap((s) => s.groups),
1469
+ [sessionsState.sessions]
1166
1470
  );
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]);
1177
- const handleOpenAddSession = useCallback3(() => {
1471
+ const statusCounts = useMemo7(
1472
+ () => allGroups.flatMap((g) => g.tabs).flatMap((t) => t.panes).filter((p) => p.kind === "claude" && p.claudeStatus).reduce((acc, p) => {
1473
+ const s = p.claudeStatus;
1474
+ if (!s) return acc;
1475
+ return { ...acc, [s]: (acc[s] ?? 0) + 1 };
1476
+ }, {}),
1477
+ [allGroups]
1478
+ );
1479
+ const handleOpenAddSession = useCallback4(() => {
1178
1480
  setMode(MODE.addSession);
1179
1481
  }, []);
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
- );
1196
- const handleCancelAddSession = useCallback3(() => {
1482
+ const handleAddSessionSelect = useCallback4((path) => {
1483
+ const name = basename2(path);
1484
+ setSessionsState((prev) => {
1485
+ const exists2 = prev.sessions.some((s) => s.name === name);
1486
+ if (exists2) return prev;
1487
+ return {
1488
+ ...prev,
1489
+ sessions: [{ name, path, groups: [] }, ...prev.sessions]
1490
+ };
1491
+ });
1492
+ setSelectedSession(name);
1197
1493
  setMode(MODE.split);
1198
1494
  }, []);
1199
- const handleDeleteSession = useCallback3((name) => {
1495
+ const handleCancelAddSession = useCallback4(() => {
1496
+ setMode(MODE.split);
1497
+ }, []);
1498
+ const handleDeleteSession = useCallback4((name) => {
1200
1499
  setPendingDeleteSession(name);
1201
1500
  setMode(MODE.deleteSession);
1202
1501
  }, []);
1203
- const handleConfirmDelete = useCallback3(() => {
1502
+ const handleConfirmDelete = useCallback4(() => {
1204
1503
  if (!pendingDeleteSession) return;
1205
- sessionCwdMap.delete(pendingDeleteSession);
1504
+ const session = sessionsState.sessions.find((s) => s.name === pendingDeleteSession);
1505
+ setSessionsState((prev) => ({
1506
+ ...prev,
1507
+ sessions: prev.sessions.filter((s) => s.name !== pendingDeleteSession)
1508
+ }));
1206
1509
  if (resolvedSession === pendingDeleteSession) {
1207
1510
  setSelectedSession(void 0);
1208
1511
  }
1209
- void swallow(() => actions.killSession(pendingDeleteSession)).then(() => void refresh());
1512
+ if (session) {
1513
+ const killAll = Promise.all(
1514
+ session.groups.map((g) => swallow(() => actions.killSession(g.sessionName)))
1515
+ );
1516
+ void killAll.then(() => void refresh());
1517
+ }
1210
1518
  setPendingDeleteSession(void 0);
1211
1519
  setMode(MODE.split);
1212
- }, [pendingDeleteSession, resolvedSession, actions, refresh]);
1213
- const handleCancelDelete = useCallback3(() => {
1520
+ }, [pendingDeleteSession, resolvedSession, sessionsState.sessions, actions, refresh]);
1521
+ const handleCancelDelete = useCallback4(() => {
1214
1522
  setPendingDeleteSession(void 0);
1215
1523
  setMode(MODE.split);
1216
1524
  }, []);
1217
- const handleNewSession = useCallback3(
1525
+ const handleNewSession = useCallback4(
1218
1526
  (sessionName) => {
1219
- actions.openEditor(sessionName);
1527
+ const cwd = selectedManagedSession?.path;
1528
+ if (!cwd) return;
1529
+ actions.openEditor(sessionName, cwd);
1220
1530
  },
1221
- [actions]
1531
+ [actions, selectedManagedSession]
1222
1532
  );
1223
- const handleConfirmNew = useCallback3(() => {
1533
+ const handleConfirmNew = useCallback4(() => {
1224
1534
  if (!resolvedSession) return;
1225
- const existingCwd = selectedGroup?.tabs[0]?.panes[0]?.pane.cwd;
1226
- const cwd = sessionCwdMap.get(resolvedSession) ?? existingCwd ?? currentCwd;
1535
+ const cwd = restoredCwd ?? selectedManagedSession?.path;
1536
+ if (!cwd) return;
1227
1537
  void actions.createSession(resolvedSession, cwd, pendingPrompt).then(() => void refresh());
1228
1538
  setPendingPrompt("");
1229
1539
  setMode(MODE.split);
1230
- }, [resolvedSession, selectedGroup, currentCwd, pendingPrompt, actions, refresh]);
1231
- const handleCancelConfirm = useCallback3(() => {
1540
+ }, [resolvedSession, restoredCwd, selectedManagedSession, pendingPrompt, actions, refresh]);
1541
+ const handleCancelConfirm = useCallback4(() => {
1232
1542
  setPendingPrompt("");
1233
1543
  setMode(MODE.split);
1234
1544
  }, []);
1235
- const handleSessionSelect = useCallback3((name) => {
1545
+ const handleSessionSelect = useCallback4((name) => {
1236
1546
  setSelectedSession(name);
1237
1547
  setFocus(FOCUS.right);
1238
1548
  }, []);
1239
- const handleSessionCursorChange = useCallback3((name) => {
1549
+ const handleSessionCursorChange = useCallback4((name) => {
1240
1550
  setSelectedSession(name);
1241
1551
  }, []);
1242
- const handleNavigate = useCallback3(
1552
+ const handleNavigate = useCallback4(
1243
1553
  (up) => {
1244
1554
  void actions.navigateToPane(up);
1245
1555
  },
1246
1556
  [actions]
1247
1557
  );
1248
- const handleBack = useCallback3(() => {
1558
+ const handleBack = useCallback4(() => {
1249
1559
  setFocus(FOCUS.left);
1250
1560
  }, []);
1251
- const handleKillPane = useCallback3(
1561
+ const handleKillPane = useCallback4(
1252
1562
  async (paneId) => {
1253
1563
  await swallow(() => actions.killPane(paneId));
1254
1564
  void refresh();
1255
1565
  },
1256
1566
  [actions, refresh]
1257
1567
  );
1258
- const handleHighlight = useCallback3(
1568
+ const handleHighlight = useCallback4(
1259
1569
  async (up) => {
1260
1570
  await swallow(() => actions.highlightWindow(up));
1261
1571
  },
1262
1572
  [actions]
1263
1573
  );
1264
- const handleUnhighlight = useCallback3(
1574
+ const handleUnhighlight = useCallback4(
1265
1575
  async (up) => {
1266
1576
  await swallow(() => actions.unhighlightWindow(up));
1267
1577
  },
1268
1578
  [actions]
1269
1579
  );
1270
- if (fetchState.isLoading) {
1271
- return /* @__PURE__ */ jsxs7(Box10, { flexDirection: "column", height: rows, children: [
1272
- /* @__PURE__ */ jsx10(Header, { title: `${APP_TITLE} v${APP_VERSION}` }),
1273
- /* @__PURE__ */ jsx10(StatusBar, { message: "Loading...", type: "info" })
1580
+ if (sessionsState.isLoading) {
1581
+ return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: rows, children: [
1582
+ /* @__PURE__ */ jsx11(Header, { title: `${APP_TITLE} v${APP_VERSION}` }),
1583
+ /* @__PURE__ */ jsx11(StatusBar, { message: "Loading...", type: "info" })
1274
1584
  ] });
1275
1585
  }
1276
1586
  if (mode === MODE.addSession) {
1277
- return /* @__PURE__ */ jsx10(
1587
+ return /* @__PURE__ */ jsx11(
1278
1588
  DirectorySearchView,
1279
1589
  {
1280
1590
  directories,
@@ -1284,9 +1594,12 @@ var ManagerView = ({
1284
1594
  );
1285
1595
  }
1286
1596
  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;
1289
- return /* @__PURE__ */ jsx10(
1597
+ const deleteSession = sessionsState.sessions.find((s) => s.name === pendingDeleteSession);
1598
+ const paneCount = deleteSession?.groups.reduce(
1599
+ (sum, g) => sum + g.tabs.reduce((s, t) => s + t.panes.length, 0),
1600
+ 0
1601
+ ) ?? 0;
1602
+ return /* @__PURE__ */ jsx11(
1290
1603
  DeleteSessionView,
1291
1604
  {
1292
1605
  sessionName: pendingDeleteSession,
@@ -1297,7 +1610,7 @@ var ManagerView = ({
1297
1610
  );
1298
1611
  }
1299
1612
  if (mode === MODE.confirm && pendingPrompt) {
1300
- return /* @__PURE__ */ jsx10(
1613
+ return /* @__PURE__ */ jsx11(
1301
1614
  ConfirmView,
1302
1615
  {
1303
1616
  selectedDir: resolvedSession ?? "",
@@ -1307,27 +1620,29 @@ var ManagerView = ({
1307
1620
  }
1308
1621
  );
1309
1622
  }
1310
- const panelHeight = rows - 5;
1623
+ const fixedRows = 3;
1624
+ const contentHeight = rows - fixedRows;
1625
+ const topHeight = Math.floor(contentHeight / 2);
1626
+ const bottomHeight = contentHeight - topHeight;
1311
1627
  const leftWidth = Math.floor(columns / 3);
1312
1628
  const rightWidth = columns - leftWidth;
1313
- return /* @__PURE__ */ jsxs7(Box10, { flexDirection: "column", height: rows, children: [
1314
- /* @__PURE__ */ jsx10(Header, { title: `${APP_TITLE} - v${APP_VERSION}` }),
1315
- /* @__PURE__ */ jsxs7(Box10, { flexDirection: "row", flexGrow: 1, children: [
1316
- /* @__PURE__ */ jsx10(
1317
- Box10,
1629
+ return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: rows, children: [
1630
+ /* @__PURE__ */ jsx11(Header, { title: `${APP_TITLE} - v${APP_VERSION}` }),
1631
+ /* @__PURE__ */ jsxs9(Box11, { flexDirection: "row", height: topHeight, children: [
1632
+ /* @__PURE__ */ jsx11(
1633
+ Box11,
1318
1634
  {
1319
1635
  flexDirection: "column",
1320
1636
  width: leftWidth,
1321
1637
  borderStyle: "round",
1322
1638
  borderColor: focus === FOCUS.left ? "green" : "gray",
1323
- children: /* @__PURE__ */ jsx10(
1639
+ children: /* @__PURE__ */ jsx11(
1324
1640
  SessionListPanel,
1325
1641
  {
1326
- sessionGroups: fetchState.data,
1642
+ sessions: sessionsState.sessions,
1327
1643
  currentSession,
1328
- sessionPathMap,
1329
1644
  isFocused: focus === FOCUS.left,
1330
- availableRows: panelHeight,
1645
+ availableRows: topHeight - 2,
1331
1646
  onSelect: handleSessionSelect,
1332
1647
  onCursorChange: handleSessionCursorChange,
1333
1648
  onDeleteSession: handleDeleteSession,
@@ -1336,20 +1651,20 @@ var ManagerView = ({
1336
1651
  )
1337
1652
  }
1338
1653
  ),
1339
- /* @__PURE__ */ jsx10(
1340
- Box10,
1654
+ /* @__PURE__ */ jsx11(
1655
+ Box11,
1341
1656
  {
1342
1657
  flexDirection: "column",
1343
1658
  width: rightWidth,
1344
1659
  borderStyle: "round",
1345
1660
  borderColor: focus === FOCUS.right ? "green" : "gray",
1346
- children: /* @__PURE__ */ jsx10(
1661
+ children: /* @__PURE__ */ jsx11(
1347
1662
  PaneListPanel,
1348
1663
  {
1349
1664
  selectedSession: resolvedSession,
1350
1665
  group: selectedGroup,
1351
1666
  isFocused: focus === FOCUS.right,
1352
- availableRows: panelHeight,
1667
+ availableRows: topHeight - 2,
1353
1668
  onNavigate: handleNavigate,
1354
1669
  onHighlight: handleHighlight,
1355
1670
  onUnhighlight: handleUnhighlight,
@@ -1361,32 +1676,67 @@ var ManagerView = ({
1361
1676
  }
1362
1677
  )
1363
1678
  ] }),
1364
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: focus === FOCUS.left ? "\u2191/\u2193 move Enter/\u2192 select n add d delete q quit" : "\u2191/\u2193 move Enter focus n new d kill Esc/\u2190 back q quit" })
1679
+ /* @__PURE__ */ jsx11(
1680
+ SessionOverviewPanel,
1681
+ {
1682
+ overallSummary: overviewResult.overallSummary,
1683
+ items: overviewResult.sessions,
1684
+ groups: allGroups,
1685
+ isLoading: overviewLoading,
1686
+ isFocused: focus === FOCUS.bottom,
1687
+ availableRows: bottomHeight,
1688
+ onBack: handleBack
1689
+ }
1690
+ ),
1691
+ /* @__PURE__ */ jsx11(
1692
+ StatusBar,
1693
+ {
1694
+ 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",
1695
+ statusCounts
1696
+ }
1697
+ )
1365
1698
  ] });
1366
1699
  };
1367
1700
 
1368
1701
  // src/cli/tui-command.ts
1369
1702
  var createTuiCommand = ({ usecases, services, infra }) => async () => {
1370
1703
  const directories = await services.directoryScan.scan();
1371
- const sessionCwdMap = /* @__PURE__ */ new Map();
1372
1704
  let instance;
1373
1705
  let pendingPrompt;
1374
1706
  let pendingSession;
1707
+ let pendingCwd;
1375
1708
  const actions = {
1376
1709
  fetchSessions: async () => {
1377
1710
  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
- }))
1711
+ const resolved = await Promise.all(
1712
+ result.sessionGroups.map(async (group) => {
1713
+ const enrichedGroup = {
1714
+ sessionName: group.sessionName,
1715
+ tabs: await Promise.all(
1716
+ group.tabs.map(async (tab) => ({
1717
+ windowIndex: tab.windowIndex,
1718
+ windowName: tab.windowName,
1719
+ panes: await Promise.all(
1720
+ tab.panes.map((up) => usecases.manager.enrichStatus(up))
1721
+ )
1722
+ }))
1723
+ )
1724
+ };
1725
+ const paneCwd = group.tabs[0]?.panes[0]?.pane.cwd ?? "";
1726
+ const path = findMatchingDirectory(paneCwd, directories) ?? paneCwd;
1727
+ return { path, group: enrichedGroup };
1728
+ })
1389
1729
  );
1730
+ const grouped = Map.groupBy(resolved, (item) => item.path || item.group.sessionName);
1731
+ return [...grouped.entries()].map(([key, items]) => ({
1732
+ name: basename3(key) || items[0].group.sessionName,
1733
+ path: items[0].path,
1734
+ groups: items.map((item) => item.group)
1735
+ }));
1736
+ },
1737
+ fetchOverview: async (sessions) => {
1738
+ const groups = sessions.flatMap((s) => s.groups);
1739
+ return await usecases.manager.fetchOverview(groups);
1390
1740
  },
1391
1741
  createSession: async (sessionName, cwd, prompt) => {
1392
1742
  await usecases.manager.createSession({ sessionName, cwd, prompt });
@@ -1403,11 +1753,12 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1403
1753
  unhighlightWindow: async (up) => {
1404
1754
  await usecases.manager.unhighlightWindow(up);
1405
1755
  },
1406
- openEditor: (sessionName) => {
1756
+ openEditor: (sessionName, cwd) => {
1407
1757
  instance.unmount();
1408
1758
  const prompt = infra.editor.open();
1409
1759
  pendingPrompt = prompt;
1410
1760
  pendingSession = sessionName;
1761
+ pendingCwd = cwd;
1411
1762
  instance = renderApp();
1412
1763
  return prompt;
1413
1764
  },
@@ -1423,17 +1774,20 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
1423
1774
  const renderApp = () => {
1424
1775
  const prompt = pendingPrompt;
1425
1776
  const session = pendingSession;
1777
+ const cwd = pendingCwd;
1426
1778
  pendingPrompt = void 0;
1427
1779
  pendingSession = void 0;
1780
+ pendingCwd = void 0;
1781
+ const rawCwd = process.cwd();
1782
+ const currentSession = basename3(findMatchingDirectory(rawCwd, directories) ?? rawCwd);
1428
1783
  return render(
1429
1784
  createElement(ManagerView, {
1430
1785
  actions,
1431
- currentSession: basename3(process.cwd()),
1432
- currentCwd: process.cwd(),
1786
+ currentSession,
1433
1787
  directories,
1434
- sessionCwdMap,
1435
1788
  restoredPrompt: prompt,
1436
- restoredSession: session
1789
+ restoredSession: session,
1790
+ restoredCwd: cwd
1437
1791
  }),
1438
1792
  { concurrent: true }
1439
1793
  );
@@ -1491,10 +1845,11 @@ var createListCommand = ({ usecases }) => async () => {
1491
1845
  console.log("No sessions found.");
1492
1846
  return;
1493
1847
  }
1494
- for (const group of result.sessionGroups) {
1848
+ const lines = result.sessionGroups.map((group) => {
1495
1849
  const paneCount = group.tabs.reduce((sum, t) => sum + t.panes.length, 0);
1496
- console.log(`${group.sessionName} (${String(paneCount)} panes)`);
1497
- }
1850
+ return `${group.sessionName} (${String(paneCount)} panes)`;
1851
+ });
1852
+ console.log(lines.join("\n"));
1498
1853
  };
1499
1854
 
1500
1855
  // src/cli/index.ts