abmux 0.0.5 → 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.
- package/dist/cli/index.js +494 -136
- 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
|
|
@@ -387,11 +474,51 @@ var createDirectoryScanService = () => ({
|
|
|
387
474
|
}
|
|
388
475
|
});
|
|
389
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
|
+
|
|
390
516
|
// src/services/index.ts
|
|
391
517
|
var createServices = (context) => ({
|
|
392
518
|
tmux: createTmuxService(context),
|
|
393
519
|
sessionDetection: createSessionDetectionService(),
|
|
394
|
-
directoryScan: createDirectoryScanService()
|
|
520
|
+
directoryScan: createDirectoryScanService(),
|
|
521
|
+
sessionSummary: createSessionSummaryService(context)
|
|
395
522
|
});
|
|
396
523
|
|
|
397
524
|
// src/utils/ShellUtils.ts
|
|
@@ -424,6 +551,9 @@ var createManagerUsecase = (context) => {
|
|
|
424
551
|
const sessionGroups = sessionDetection.groupBySession({ panes });
|
|
425
552
|
return { sessionGroups };
|
|
426
553
|
},
|
|
554
|
+
fetchOverview: async (groups) => {
|
|
555
|
+
return await context.services.sessionSummary.fetchSummary(groups);
|
|
556
|
+
},
|
|
427
557
|
enrichStatus: async (up) => {
|
|
428
558
|
if (up.kind !== "claude") return up;
|
|
429
559
|
try {
|
|
@@ -467,7 +597,7 @@ var createUsecases = (context) => ({
|
|
|
467
597
|
// package.json
|
|
468
598
|
var package_default = {
|
|
469
599
|
name: "abmux",
|
|
470
|
-
version: "0.0.
|
|
600
|
+
version: "0.0.6",
|
|
471
601
|
repository: {
|
|
472
602
|
type: "git",
|
|
473
603
|
url: "https://github.com/cut0/abmux.git"
|
|
@@ -537,8 +667,8 @@ import { createElement } from "react";
|
|
|
537
667
|
|
|
538
668
|
// src/components/ManagerView.tsx
|
|
539
669
|
import { basename as basename2 } from "node:path";
|
|
540
|
-
import { Box as
|
|
541
|
-
import { useCallback as
|
|
670
|
+
import { Box as Box11 } from "ink";
|
|
671
|
+
import { useCallback as useCallback4, useMemo as useMemo7, useRef as useRef3, useState as useState6 } from "react";
|
|
542
672
|
|
|
543
673
|
// src/components/shared/Header.tsx
|
|
544
674
|
import { Box, Text } from "ink";
|
|
@@ -549,24 +679,57 @@ var Header = ({ title }) => {
|
|
|
549
679
|
|
|
550
680
|
// src/components/shared/StatusBar.tsx
|
|
551
681
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
552
|
-
import {
|
|
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
|
+
};
|
|
553
691
|
var COLOR_MAP = {
|
|
554
692
|
success: "green",
|
|
555
693
|
error: "red",
|
|
556
694
|
info: "gray"
|
|
557
695
|
};
|
|
558
|
-
var
|
|
559
|
-
|
|
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
|
+
] });
|
|
560
723
|
};
|
|
561
724
|
|
|
562
725
|
// src/components/shared/DirectorySelect.tsx
|
|
563
726
|
import { Box as Box3, Text as Text3, useApp, useInput } from "ink";
|
|
564
|
-
import { useCallback, useMemo as
|
|
727
|
+
import { useCallback, useMemo as useMemo3, useState } from "react";
|
|
565
728
|
|
|
566
729
|
// src/hooks/use-scroll.ts
|
|
567
|
-
import { useMemo } from "react";
|
|
730
|
+
import { useMemo as useMemo2 } from "react";
|
|
568
731
|
var useScroll = (cursor, totalItems, availableRows) => {
|
|
569
|
-
return
|
|
732
|
+
return useMemo2(() => {
|
|
570
733
|
const visibleCount = Math.max(1, availableRows);
|
|
571
734
|
if (totalItems <= visibleCount) {
|
|
572
735
|
return { scrollOffset: 0, visibleCount };
|
|
@@ -592,7 +755,7 @@ var findMatchingDirectory = (path, directories) => directories.filter((dir) => p
|
|
|
592
755
|
);
|
|
593
756
|
|
|
594
757
|
// src/components/shared/DirectorySelect.tsx
|
|
595
|
-
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
758
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
596
759
|
var sortSessions = (sessions, currentSession) => {
|
|
597
760
|
const current = sessions.filter((s) => s.name === currentSession);
|
|
598
761
|
const rest = sessions.filter((s) => s.name !== currentSession);
|
|
@@ -610,11 +773,11 @@ var SessionListPanel = ({
|
|
|
610
773
|
}) => {
|
|
611
774
|
const { exit } = useApp();
|
|
612
775
|
const [cursor, setCursor] = useState(0);
|
|
613
|
-
const sortedSessions =
|
|
776
|
+
const sortedSessions = useMemo3(
|
|
614
777
|
() => sortSessions(sessions, currentSession),
|
|
615
778
|
[sessions, currentSession]
|
|
616
779
|
);
|
|
617
|
-
const names =
|
|
780
|
+
const names = useMemo3(() => sortedSessions.map((s) => s.name), [sortedSessions]);
|
|
618
781
|
const clampedCursor = cursor >= names.length ? Math.max(0, names.length - 1) : cursor;
|
|
619
782
|
if (clampedCursor !== cursor) {
|
|
620
783
|
setCursor(clampedCursor);
|
|
@@ -665,10 +828,10 @@ var SessionListPanel = ({
|
|
|
665
828
|
},
|
|
666
829
|
{ isActive: isFocused }
|
|
667
830
|
);
|
|
668
|
-
return /* @__PURE__ */
|
|
669
|
-
/* @__PURE__ */
|
|
831
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
|
|
832
|
+
/* @__PURE__ */ jsxs2(Box3, { paddingLeft: 1, children: [
|
|
670
833
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: isFocused ? "green" : "gray", children: "Sessions" }),
|
|
671
|
-
/* @__PURE__ */
|
|
834
|
+
/* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
|
|
672
835
|
" ",
|
|
673
836
|
"(",
|
|
674
837
|
clampedCursor + 1,
|
|
@@ -681,7 +844,7 @@ var SessionListPanel = ({
|
|
|
681
844
|
const globalIndex = scrollOffset + i;
|
|
682
845
|
const isHighlighted = globalIndex === clampedCursor;
|
|
683
846
|
const isCurrent = session.name === currentSession;
|
|
684
|
-
return /* @__PURE__ */
|
|
847
|
+
return /* @__PURE__ */ jsxs2(Box3, { paddingLeft: 1, gap: 1, children: [
|
|
685
848
|
/* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
|
|
686
849
|
/* @__PURE__ */ jsx3(Text3, { color: isHighlighted ? "green" : "cyan", bold: isHighlighted, wrap: "truncate", children: session.path ? formatCwd(session.path) : session.name }),
|
|
687
850
|
isCurrent && /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "(cwd)" })
|
|
@@ -695,21 +858,21 @@ import { Box as Box6, Text as Text6 } from "ink";
|
|
|
695
858
|
|
|
696
859
|
// src/components/PaneListView.tsx
|
|
697
860
|
import { Box as Box5, Text as Text5, useApp as useApp2, useInput as useInput2 } from "ink";
|
|
698
|
-
import { useCallback as useCallback2, useMemo as
|
|
861
|
+
import { useCallback as useCallback2, useMemo as useMemo4, useRef, useState as useState2 } from "react";
|
|
699
862
|
|
|
700
863
|
// src/components/sessions/PaneItem.tsx
|
|
701
864
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
702
|
-
import { jsx as jsx4, jsxs as
|
|
865
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
703
866
|
var PaneItem = ({ unifiedPane, isHighlighted }) => {
|
|
704
867
|
const { pane, kind, claudeStatus, claudeTitle } = unifiedPane;
|
|
705
868
|
if (kind === "claude") {
|
|
706
869
|
const icon = pane.title.charAt(0);
|
|
707
870
|
const statusLabel = claudeStatus ? SESSION_STATUS_LABEL[claudeStatus] : "";
|
|
708
871
|
const statusColor = claudeStatus ? SESSION_STATUS_COLOR[claudeStatus] : "gray";
|
|
709
|
-
return /* @__PURE__ */
|
|
872
|
+
return /* @__PURE__ */ jsxs3(Box4, { paddingLeft: 3, gap: 1, children: [
|
|
710
873
|
/* @__PURE__ */ jsx4(Text4, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
|
|
711
874
|
/* @__PURE__ */ jsx4(Text4, { color: "#FF8C00", children: icon }),
|
|
712
|
-
/* @__PURE__ */
|
|
875
|
+
/* @__PURE__ */ jsxs3(Text4, { color: statusColor, children: [
|
|
713
876
|
"[",
|
|
714
877
|
statusLabel,
|
|
715
878
|
"]"
|
|
@@ -718,7 +881,7 @@ var PaneItem = ({ unifiedPane, isHighlighted }) => {
|
|
|
718
881
|
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: pane.paneId })
|
|
719
882
|
] });
|
|
720
883
|
}
|
|
721
|
-
return /* @__PURE__ */
|
|
884
|
+
return /* @__PURE__ */ jsxs3(Box4, { paddingLeft: 3, gap: 1, children: [
|
|
722
885
|
/* @__PURE__ */ jsx4(Text4, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
|
|
723
886
|
kind === "available" && /* @__PURE__ */ jsx4(Text4, { color: "#4AA8D8", children: "\u25CB" }),
|
|
724
887
|
kind === "busy" && /* @__PURE__ */ jsx4(Text4, { color: "#E05252", children: "\u25CF" }),
|
|
@@ -728,7 +891,7 @@ var PaneItem = ({ unifiedPane, isHighlighted }) => {
|
|
|
728
891
|
};
|
|
729
892
|
|
|
730
893
|
// src/components/PaneListView.tsx
|
|
731
|
-
import { jsx as jsx5, jsxs as
|
|
894
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
732
895
|
var PaneListView = ({
|
|
733
896
|
selectedSession,
|
|
734
897
|
group,
|
|
@@ -744,7 +907,7 @@ var PaneListView = ({
|
|
|
744
907
|
const { exit } = useApp2();
|
|
745
908
|
const [cursor, setCursor] = useState2(0);
|
|
746
909
|
const highlightedRef = useRef(void 0);
|
|
747
|
-
const panes =
|
|
910
|
+
const panes = useMemo4(() => group.tabs.flatMap((t) => t.panes), [group]);
|
|
748
911
|
const clampedCursor = cursor >= panes.length ? Math.max(0, panes.length - 1) : cursor;
|
|
749
912
|
if (clampedCursor !== cursor) {
|
|
750
913
|
setCursor(clampedCursor);
|
|
@@ -755,7 +918,7 @@ var PaneListView = ({
|
|
|
755
918
|
panes.length,
|
|
756
919
|
availableRows - reservedLines
|
|
757
920
|
);
|
|
758
|
-
const visiblePanes =
|
|
921
|
+
const visiblePanes = useMemo4(
|
|
759
922
|
() => panes.slice(scrollOffset, scrollOffset + visibleCount),
|
|
760
923
|
[panes, scrollOffset, visibleCount]
|
|
761
924
|
);
|
|
@@ -824,10 +987,10 @@ var PaneListView = ({
|
|
|
824
987
|
},
|
|
825
988
|
{ isActive: isFocused }
|
|
826
989
|
);
|
|
827
|
-
return /* @__PURE__ */
|
|
828
|
-
/* @__PURE__ */
|
|
990
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
991
|
+
/* @__PURE__ */ jsxs4(Box5, { paddingLeft: 1, children: [
|
|
829
992
|
/* @__PURE__ */ jsx5(Text5, { bold: true, color: isFocused ? "green" : "gray", children: "Panes" }),
|
|
830
|
-
/* @__PURE__ */
|
|
993
|
+
/* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
|
|
831
994
|
" ",
|
|
832
995
|
selectedSession,
|
|
833
996
|
" (",
|
|
@@ -883,15 +1046,140 @@ var PaneListPanel = ({
|
|
|
883
1046
|
);
|
|
884
1047
|
};
|
|
885
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
|
+
|
|
886
1174
|
// src/components/ConfirmView.tsx
|
|
887
|
-
import { Box as
|
|
1175
|
+
import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
|
|
888
1176
|
|
|
889
1177
|
// src/hooks/use-terminal-size.ts
|
|
890
1178
|
import { useStdout } from "ink";
|
|
891
|
-
import { useEffect, useState as
|
|
1179
|
+
import { useEffect, useState as useState4 } from "react";
|
|
892
1180
|
var useTerminalSize = () => {
|
|
893
1181
|
const { stdout } = useStdout();
|
|
894
|
-
const [size, setSize] =
|
|
1182
|
+
const [size, setSize] = useState4({
|
|
895
1183
|
rows: stdout.rows ?? 24,
|
|
896
1184
|
columns: stdout.columns ?? 80
|
|
897
1185
|
});
|
|
@@ -911,12 +1199,12 @@ var useTerminalSize = () => {
|
|
|
911
1199
|
};
|
|
912
1200
|
|
|
913
1201
|
// src/components/ConfirmView.tsx
|
|
914
|
-
import { jsx as
|
|
1202
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
915
1203
|
var ConfirmView = ({ selectedDir, prompt, onConfirm, onCancel }) => {
|
|
916
1204
|
const { rows } = useTerminalSize();
|
|
917
1205
|
const previewLines = prompt.split("\n");
|
|
918
1206
|
const maxPreview = Math.min(previewLines.length, rows - 6);
|
|
919
|
-
|
|
1207
|
+
useInput4((_input, key) => {
|
|
920
1208
|
if (key.return) {
|
|
921
1209
|
onConfirm();
|
|
922
1210
|
return;
|
|
@@ -925,30 +1213,30 @@ var ConfirmView = ({ selectedDir, prompt, onConfirm, onCancel }) => {
|
|
|
925
1213
|
onCancel();
|
|
926
1214
|
}
|
|
927
1215
|
});
|
|
928
|
-
return /* @__PURE__ */
|
|
929
|
-
/* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
931
|
-
/* @__PURE__ */
|
|
932
|
-
previewLines.slice(0, maxPreview).map((line, i) => /* @__PURE__ */
|
|
933
|
-
previewLines.length > maxPreview && /* @__PURE__ */
|
|
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: [
|
|
934
1222
|
"... (",
|
|
935
1223
|
previewLines.length - maxPreview,
|
|
936
1224
|
" more lines)"
|
|
937
1225
|
] })
|
|
938
1226
|
] }),
|
|
939
|
-
/* @__PURE__ */
|
|
940
|
-
/* @__PURE__ */
|
|
941
|
-
/* @__PURE__ */
|
|
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" })
|
|
942
1230
|
] })
|
|
943
1231
|
] });
|
|
944
1232
|
};
|
|
945
1233
|
|
|
946
1234
|
// src/components/DeleteSessionView.tsx
|
|
947
|
-
import { Box as
|
|
948
|
-
import { jsx as
|
|
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";
|
|
949
1237
|
var DeleteSessionView = ({ sessionName, paneCount, onConfirm, onCancel }) => {
|
|
950
1238
|
const { rows } = useTerminalSize();
|
|
951
|
-
|
|
1239
|
+
useInput5((_input, key) => {
|
|
952
1240
|
if (key.return) {
|
|
953
1241
|
onConfirm();
|
|
954
1242
|
return;
|
|
@@ -957,33 +1245,33 @@ var DeleteSessionView = ({ sessionName, paneCount, onConfirm, onCancel }) => {
|
|
|
957
1245
|
onCancel();
|
|
958
1246
|
}
|
|
959
1247
|
});
|
|
960
|
-
return /* @__PURE__ */
|
|
961
|
-
/* @__PURE__ */
|
|
962
|
-
/* @__PURE__ */
|
|
963
|
-
/* @__PURE__ */
|
|
964
|
-
/* @__PURE__ */
|
|
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: [
|
|
965
1253
|
"Session: ",
|
|
966
|
-
/* @__PURE__ */
|
|
1254
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: sessionName })
|
|
967
1255
|
] }),
|
|
968
|
-
/* @__PURE__ */
|
|
1256
|
+
/* @__PURE__ */ jsxs7(Text9, { children: [
|
|
969
1257
|
"Panes: ",
|
|
970
|
-
/* @__PURE__ */
|
|
1258
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: paneCount })
|
|
971
1259
|
] }),
|
|
972
|
-
/* @__PURE__ */
|
|
1260
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "All processes in this session will be terminated." })
|
|
973
1261
|
] }),
|
|
974
|
-
/* @__PURE__ */
|
|
975
|
-
/* @__PURE__ */
|
|
976
|
-
/* @__PURE__ */
|
|
977
|
-
/* @__PURE__ */
|
|
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" })
|
|
978
1266
|
] })
|
|
979
1267
|
] });
|
|
980
1268
|
};
|
|
981
1269
|
|
|
982
1270
|
// src/components/DirectorySearchView.tsx
|
|
983
|
-
import { Box as
|
|
984
|
-
import { useMemo as
|
|
1271
|
+
import { Box as Box10, Text as Text10, useInput as useInput6 } from "ink";
|
|
1272
|
+
import { useMemo as useMemo6, useState as useState5 } from "react";
|
|
985
1273
|
import { basename } from "node:path";
|
|
986
|
-
import { jsx as
|
|
1274
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
987
1275
|
var formatPath = (path) => {
|
|
988
1276
|
const home = process.env["HOME"] ?? "";
|
|
989
1277
|
if (home && path.startsWith(home)) {
|
|
@@ -993,9 +1281,9 @@ var formatPath = (path) => {
|
|
|
993
1281
|
};
|
|
994
1282
|
var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
|
|
995
1283
|
const { rows } = useTerminalSize();
|
|
996
|
-
const [query, setQuery] =
|
|
997
|
-
const [cursor, setCursor] =
|
|
998
|
-
const filtered =
|
|
1284
|
+
const [query, setQuery] = useState5("");
|
|
1285
|
+
const [cursor, setCursor] = useState5(0);
|
|
1286
|
+
const filtered = useMemo6(() => {
|
|
999
1287
|
if (!query) return directories;
|
|
1000
1288
|
const lower = query.toLowerCase();
|
|
1001
1289
|
return directories.filter((d) => {
|
|
@@ -1010,11 +1298,11 @@ var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
|
|
|
1010
1298
|
}
|
|
1011
1299
|
const listHeight = rows - 6;
|
|
1012
1300
|
const { scrollOffset, visibleCount } = useScroll(clampedCursor, filtered.length, listHeight);
|
|
1013
|
-
const visibleItems =
|
|
1301
|
+
const visibleItems = useMemo6(
|
|
1014
1302
|
() => filtered.slice(scrollOffset, scrollOffset + visibleCount),
|
|
1015
1303
|
[filtered, scrollOffset, visibleCount]
|
|
1016
1304
|
);
|
|
1017
|
-
|
|
1305
|
+
useInput6((input, key) => {
|
|
1018
1306
|
if (key.escape) {
|
|
1019
1307
|
onCancel();
|
|
1020
1308
|
return;
|
|
@@ -1042,33 +1330,50 @@ var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
|
|
|
1042
1330
|
setCursor(0);
|
|
1043
1331
|
}
|
|
1044
1332
|
});
|
|
1045
|
-
return /* @__PURE__ */
|
|
1046
|
-
/* @__PURE__ */
|
|
1047
|
-
/* @__PURE__ */
|
|
1048
|
-
/* @__PURE__ */
|
|
1049
|
-
/* @__PURE__ */
|
|
1050
|
-
/* @__PURE__ */
|
|
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..." })
|
|
1051
1339
|
] }),
|
|
1052
|
-
/* @__PURE__ */
|
|
1340
|
+
/* @__PURE__ */ jsx10(Box10, { paddingLeft: 1, children: /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
|
|
1053
1341
|
filtered.length,
|
|
1054
1342
|
"/",
|
|
1055
1343
|
directories.length
|
|
1056
1344
|
] }) }),
|
|
1057
|
-
/* @__PURE__ */
|
|
1345
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleItems.map((dir, i) => {
|
|
1058
1346
|
const globalIndex = scrollOffset + i;
|
|
1059
1347
|
const isHighlighted = globalIndex === clampedCursor;
|
|
1060
|
-
return /* @__PURE__ */
|
|
1061
|
-
/* @__PURE__ */
|
|
1062
|
-
/* @__PURE__ */
|
|
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) })
|
|
1063
1351
|
] }, dir);
|
|
1064
1352
|
}) }),
|
|
1065
|
-
/* @__PURE__ */
|
|
1066
|
-
/* @__PURE__ */
|
|
1067
|
-
/* @__PURE__ */
|
|
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" })
|
|
1068
1356
|
] })
|
|
1069
1357
|
] });
|
|
1070
1358
|
};
|
|
1071
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
|
+
|
|
1072
1377
|
// src/utils/PromiseUtils.ts
|
|
1073
1378
|
var swallow = async (fn) => {
|
|
1074
1379
|
try {
|
|
@@ -1078,7 +1383,7 @@ var swallow = async (fn) => {
|
|
|
1078
1383
|
};
|
|
1079
1384
|
|
|
1080
1385
|
// src/components/ManagerView.tsx
|
|
1081
|
-
import { jsx as
|
|
1386
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1082
1387
|
var MODE = {
|
|
1083
1388
|
split: "split",
|
|
1084
1389
|
confirm: "confirm",
|
|
@@ -1087,9 +1392,11 @@ var MODE = {
|
|
|
1087
1392
|
};
|
|
1088
1393
|
var FOCUS = {
|
|
1089
1394
|
left: "left",
|
|
1090
|
-
right: "right"
|
|
1395
|
+
right: "right",
|
|
1396
|
+
bottom: "bottom"
|
|
1091
1397
|
};
|
|
1092
1398
|
var POLL_INTERVAL = 3e3;
|
|
1399
|
+
var OVERVIEW_POLL_INTERVAL = 6e4;
|
|
1093
1400
|
var ManagerView = ({
|
|
1094
1401
|
actions,
|
|
1095
1402
|
currentSession,
|
|
@@ -1099,16 +1406,22 @@ var ManagerView = ({
|
|
|
1099
1406
|
restoredCwd
|
|
1100
1407
|
}) => {
|
|
1101
1408
|
const { rows, columns } = useTerminalSize();
|
|
1102
|
-
const [sessionsState, setSessionsState] =
|
|
1409
|
+
const [sessionsState, setSessionsState] = useState6({
|
|
1103
1410
|
sessions: [],
|
|
1104
1411
|
isLoading: true
|
|
1105
1412
|
});
|
|
1106
|
-
const [mode, setMode] =
|
|
1107
|
-
const [focus, setFocus] =
|
|
1108
|
-
const [selectedSession, setSelectedSession] =
|
|
1109
|
-
const [pendingPrompt, setPendingPrompt] =
|
|
1110
|
-
const [pendingDeleteSession, setPendingDeleteSession] =
|
|
1111
|
-
const
|
|
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 () => {
|
|
1112
1425
|
try {
|
|
1113
1426
|
const fetched = await actions.fetchSessions();
|
|
1114
1427
|
setSessionsState((prev) => {
|
|
@@ -1122,31 +1435,51 @@ var ManagerView = ({
|
|
|
1122
1435
|
setSessionsState((prev) => ({ ...prev, isLoading: false }));
|
|
1123
1436
|
}
|
|
1124
1437
|
}, [actions]);
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
+
);
|
|
1134
1455
|
const resolvedSession = selectedSession ?? sessionsState.sessions[0]?.name;
|
|
1135
|
-
const selectedManagedSession =
|
|
1456
|
+
const selectedManagedSession = useMemo7(
|
|
1136
1457
|
() => sessionsState.sessions.find((s) => s.name === resolvedSession),
|
|
1137
1458
|
[sessionsState.sessions, resolvedSession]
|
|
1138
1459
|
);
|
|
1139
|
-
const selectedGroup =
|
|
1460
|
+
const selectedGroup = useMemo7(() => {
|
|
1140
1461
|
if (!selectedManagedSession) return void 0;
|
|
1141
1462
|
return {
|
|
1142
1463
|
sessionName: selectedManagedSession.name,
|
|
1143
1464
|
tabs: selectedManagedSession.groups.flatMap((g) => g.tabs)
|
|
1144
1465
|
};
|
|
1145
1466
|
}, [selectedManagedSession]);
|
|
1146
|
-
const
|
|
1467
|
+
const allGroups = useMemo7(
|
|
1468
|
+
() => sessionsState.sessions.flatMap((s) => s.groups),
|
|
1469
|
+
[sessionsState.sessions]
|
|
1470
|
+
);
|
|
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(() => {
|
|
1147
1480
|
setMode(MODE.addSession);
|
|
1148
1481
|
}, []);
|
|
1149
|
-
const handleAddSessionSelect =
|
|
1482
|
+
const handleAddSessionSelect = useCallback4((path) => {
|
|
1150
1483
|
const name = basename2(path);
|
|
1151
1484
|
setSessionsState((prev) => {
|
|
1152
1485
|
const exists2 = prev.sessions.some((s) => s.name === name);
|
|
@@ -1159,14 +1492,14 @@ var ManagerView = ({
|
|
|
1159
1492
|
setSelectedSession(name);
|
|
1160
1493
|
setMode(MODE.split);
|
|
1161
1494
|
}, []);
|
|
1162
|
-
const handleCancelAddSession =
|
|
1495
|
+
const handleCancelAddSession = useCallback4(() => {
|
|
1163
1496
|
setMode(MODE.split);
|
|
1164
1497
|
}, []);
|
|
1165
|
-
const handleDeleteSession =
|
|
1498
|
+
const handleDeleteSession = useCallback4((name) => {
|
|
1166
1499
|
setPendingDeleteSession(name);
|
|
1167
1500
|
setMode(MODE.deleteSession);
|
|
1168
1501
|
}, []);
|
|
1169
|
-
const handleConfirmDelete =
|
|
1502
|
+
const handleConfirmDelete = useCallback4(() => {
|
|
1170
1503
|
if (!pendingDeleteSession) return;
|
|
1171
1504
|
const session = sessionsState.sessions.find((s) => s.name === pendingDeleteSession);
|
|
1172
1505
|
setSessionsState((prev) => ({
|
|
@@ -1185,11 +1518,11 @@ var ManagerView = ({
|
|
|
1185
1518
|
setPendingDeleteSession(void 0);
|
|
1186
1519
|
setMode(MODE.split);
|
|
1187
1520
|
}, [pendingDeleteSession, resolvedSession, sessionsState.sessions, actions, refresh]);
|
|
1188
|
-
const handleCancelDelete =
|
|
1521
|
+
const handleCancelDelete = useCallback4(() => {
|
|
1189
1522
|
setPendingDeleteSession(void 0);
|
|
1190
1523
|
setMode(MODE.split);
|
|
1191
1524
|
}, []);
|
|
1192
|
-
const handleNewSession =
|
|
1525
|
+
const handleNewSession = useCallback4(
|
|
1193
1526
|
(sessionName) => {
|
|
1194
1527
|
const cwd = selectedManagedSession?.path;
|
|
1195
1528
|
if (!cwd) return;
|
|
@@ -1197,7 +1530,7 @@ var ManagerView = ({
|
|
|
1197
1530
|
},
|
|
1198
1531
|
[actions, selectedManagedSession]
|
|
1199
1532
|
);
|
|
1200
|
-
const handleConfirmNew =
|
|
1533
|
+
const handleConfirmNew = useCallback4(() => {
|
|
1201
1534
|
if (!resolvedSession) return;
|
|
1202
1535
|
const cwd = restoredCwd ?? selectedManagedSession?.path;
|
|
1203
1536
|
if (!cwd) return;
|
|
@@ -1205,53 +1538,53 @@ var ManagerView = ({
|
|
|
1205
1538
|
setPendingPrompt("");
|
|
1206
1539
|
setMode(MODE.split);
|
|
1207
1540
|
}, [resolvedSession, restoredCwd, selectedManagedSession, pendingPrompt, actions, refresh]);
|
|
1208
|
-
const handleCancelConfirm =
|
|
1541
|
+
const handleCancelConfirm = useCallback4(() => {
|
|
1209
1542
|
setPendingPrompt("");
|
|
1210
1543
|
setMode(MODE.split);
|
|
1211
1544
|
}, []);
|
|
1212
|
-
const handleSessionSelect =
|
|
1545
|
+
const handleSessionSelect = useCallback4((name) => {
|
|
1213
1546
|
setSelectedSession(name);
|
|
1214
1547
|
setFocus(FOCUS.right);
|
|
1215
1548
|
}, []);
|
|
1216
|
-
const handleSessionCursorChange =
|
|
1549
|
+
const handleSessionCursorChange = useCallback4((name) => {
|
|
1217
1550
|
setSelectedSession(name);
|
|
1218
1551
|
}, []);
|
|
1219
|
-
const handleNavigate =
|
|
1552
|
+
const handleNavigate = useCallback4(
|
|
1220
1553
|
(up) => {
|
|
1221
1554
|
void actions.navigateToPane(up);
|
|
1222
1555
|
},
|
|
1223
1556
|
[actions]
|
|
1224
1557
|
);
|
|
1225
|
-
const handleBack =
|
|
1558
|
+
const handleBack = useCallback4(() => {
|
|
1226
1559
|
setFocus(FOCUS.left);
|
|
1227
1560
|
}, []);
|
|
1228
|
-
const handleKillPane =
|
|
1561
|
+
const handleKillPane = useCallback4(
|
|
1229
1562
|
async (paneId) => {
|
|
1230
1563
|
await swallow(() => actions.killPane(paneId));
|
|
1231
1564
|
void refresh();
|
|
1232
1565
|
},
|
|
1233
1566
|
[actions, refresh]
|
|
1234
1567
|
);
|
|
1235
|
-
const handleHighlight =
|
|
1568
|
+
const handleHighlight = useCallback4(
|
|
1236
1569
|
async (up) => {
|
|
1237
1570
|
await swallow(() => actions.highlightWindow(up));
|
|
1238
1571
|
},
|
|
1239
1572
|
[actions]
|
|
1240
1573
|
);
|
|
1241
|
-
const handleUnhighlight =
|
|
1574
|
+
const handleUnhighlight = useCallback4(
|
|
1242
1575
|
async (up) => {
|
|
1243
1576
|
await swallow(() => actions.unhighlightWindow(up));
|
|
1244
1577
|
},
|
|
1245
1578
|
[actions]
|
|
1246
1579
|
);
|
|
1247
1580
|
if (sessionsState.isLoading) {
|
|
1248
|
-
return /* @__PURE__ */
|
|
1249
|
-
/* @__PURE__ */
|
|
1250
|
-
/* @__PURE__ */
|
|
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" })
|
|
1251
1584
|
] });
|
|
1252
1585
|
}
|
|
1253
1586
|
if (mode === MODE.addSession) {
|
|
1254
|
-
return /* @__PURE__ */
|
|
1587
|
+
return /* @__PURE__ */ jsx11(
|
|
1255
1588
|
DirectorySearchView,
|
|
1256
1589
|
{
|
|
1257
1590
|
directories,
|
|
@@ -1266,7 +1599,7 @@ var ManagerView = ({
|
|
|
1266
1599
|
(sum, g) => sum + g.tabs.reduce((s, t) => s + t.panes.length, 0),
|
|
1267
1600
|
0
|
|
1268
1601
|
) ?? 0;
|
|
1269
|
-
return /* @__PURE__ */
|
|
1602
|
+
return /* @__PURE__ */ jsx11(
|
|
1270
1603
|
DeleteSessionView,
|
|
1271
1604
|
{
|
|
1272
1605
|
sessionName: pendingDeleteSession,
|
|
@@ -1277,7 +1610,7 @@ var ManagerView = ({
|
|
|
1277
1610
|
);
|
|
1278
1611
|
}
|
|
1279
1612
|
if (mode === MODE.confirm && pendingPrompt) {
|
|
1280
|
-
return /* @__PURE__ */
|
|
1613
|
+
return /* @__PURE__ */ jsx11(
|
|
1281
1614
|
ConfirmView,
|
|
1282
1615
|
{
|
|
1283
1616
|
selectedDir: resolvedSession ?? "",
|
|
@@ -1287,26 +1620,29 @@ var ManagerView = ({
|
|
|
1287
1620
|
}
|
|
1288
1621
|
);
|
|
1289
1622
|
}
|
|
1290
|
-
const
|
|
1623
|
+
const fixedRows = 3;
|
|
1624
|
+
const contentHeight = rows - fixedRows;
|
|
1625
|
+
const topHeight = Math.floor(contentHeight / 2);
|
|
1626
|
+
const bottomHeight = contentHeight - topHeight;
|
|
1291
1627
|
const leftWidth = Math.floor(columns / 3);
|
|
1292
1628
|
const rightWidth = columns - leftWidth;
|
|
1293
|
-
return /* @__PURE__ */
|
|
1294
|
-
/* @__PURE__ */
|
|
1295
|
-
/* @__PURE__ */
|
|
1296
|
-
/* @__PURE__ */
|
|
1297
|
-
|
|
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,
|
|
1298
1634
|
{
|
|
1299
1635
|
flexDirection: "column",
|
|
1300
1636
|
width: leftWidth,
|
|
1301
1637
|
borderStyle: "round",
|
|
1302
1638
|
borderColor: focus === FOCUS.left ? "green" : "gray",
|
|
1303
|
-
children: /* @__PURE__ */
|
|
1639
|
+
children: /* @__PURE__ */ jsx11(
|
|
1304
1640
|
SessionListPanel,
|
|
1305
1641
|
{
|
|
1306
1642
|
sessions: sessionsState.sessions,
|
|
1307
1643
|
currentSession,
|
|
1308
1644
|
isFocused: focus === FOCUS.left,
|
|
1309
|
-
availableRows:
|
|
1645
|
+
availableRows: topHeight - 2,
|
|
1310
1646
|
onSelect: handleSessionSelect,
|
|
1311
1647
|
onCursorChange: handleSessionCursorChange,
|
|
1312
1648
|
onDeleteSession: handleDeleteSession,
|
|
@@ -1315,20 +1651,20 @@ var ManagerView = ({
|
|
|
1315
1651
|
)
|
|
1316
1652
|
}
|
|
1317
1653
|
),
|
|
1318
|
-
/* @__PURE__ */
|
|
1319
|
-
|
|
1654
|
+
/* @__PURE__ */ jsx11(
|
|
1655
|
+
Box11,
|
|
1320
1656
|
{
|
|
1321
1657
|
flexDirection: "column",
|
|
1322
1658
|
width: rightWidth,
|
|
1323
1659
|
borderStyle: "round",
|
|
1324
1660
|
borderColor: focus === FOCUS.right ? "green" : "gray",
|
|
1325
|
-
children: /* @__PURE__ */
|
|
1661
|
+
children: /* @__PURE__ */ jsx11(
|
|
1326
1662
|
PaneListPanel,
|
|
1327
1663
|
{
|
|
1328
1664
|
selectedSession: resolvedSession,
|
|
1329
1665
|
group: selectedGroup,
|
|
1330
1666
|
isFocused: focus === FOCUS.right,
|
|
1331
|
-
availableRows:
|
|
1667
|
+
availableRows: topHeight - 2,
|
|
1332
1668
|
onNavigate: handleNavigate,
|
|
1333
1669
|
onHighlight: handleHighlight,
|
|
1334
1670
|
onUnhighlight: handleUnhighlight,
|
|
@@ -1340,7 +1676,25 @@ var ManagerView = ({
|
|
|
1340
1676
|
}
|
|
1341
1677
|
)
|
|
1342
1678
|
] }),
|
|
1343
|
-
/* @__PURE__ */
|
|
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
|
+
)
|
|
1344
1698
|
] });
|
|
1345
1699
|
};
|
|
1346
1700
|
|
|
@@ -1380,6 +1734,10 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
|
|
|
1380
1734
|
groups: items.map((item) => item.group)
|
|
1381
1735
|
}));
|
|
1382
1736
|
},
|
|
1737
|
+
fetchOverview: async (sessions) => {
|
|
1738
|
+
const groups = sessions.flatMap((s) => s.groups);
|
|
1739
|
+
return await usecases.manager.fetchOverview(groups);
|
|
1740
|
+
},
|
|
1383
1741
|
createSession: async (sessionName, cwd, prompt) => {
|
|
1384
1742
|
await usecases.manager.createSession({ sessionName, cwd, prompt });
|
|
1385
1743
|
},
|