abmux 0.0.5 → 0.0.7
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 +492 -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.7",
|
|
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,137 @@ 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
|
+
const reservedLines = 3;
|
|
1098
|
+
const { scrollOffset, visibleCount } = useScroll(
|
|
1099
|
+
clampedCursor,
|
|
1100
|
+
lines.length,
|
|
1101
|
+
availableRows - reservedLines
|
|
1102
|
+
);
|
|
1103
|
+
const visibleLines = useMemo5(
|
|
1104
|
+
() => lines.slice(scrollOffset, scrollOffset + visibleCount),
|
|
1105
|
+
[lines, scrollOffset, visibleCount]
|
|
1106
|
+
);
|
|
1107
|
+
const moveCursor = useCallback3(
|
|
1108
|
+
(next) => {
|
|
1109
|
+
setCursor(Math.max(0, Math.min(lines.length - 1, next)));
|
|
1110
|
+
},
|
|
1111
|
+
[lines.length]
|
|
1112
|
+
);
|
|
1113
|
+
useInput3(
|
|
1114
|
+
(input, key) => {
|
|
1115
|
+
if (input === "q") {
|
|
1116
|
+
exit();
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (key.escape || key.leftArrow) {
|
|
1120
|
+
onBack();
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (key.upArrow) {
|
|
1124
|
+
moveCursor(clampedCursor - 1);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (key.downArrow) {
|
|
1128
|
+
moveCursor(clampedCursor + 1);
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
{ isActive: isFocused }
|
|
1132
|
+
);
|
|
1133
|
+
return /* @__PURE__ */ jsxs5(
|
|
1134
|
+
Box7,
|
|
1135
|
+
{
|
|
1136
|
+
flexDirection: "column",
|
|
1137
|
+
height: availableRows,
|
|
1138
|
+
borderStyle: "round",
|
|
1139
|
+
borderColor: isFocused ? "green" : "gray",
|
|
1140
|
+
children: [
|
|
1141
|
+
/* @__PURE__ */ jsxs5(Box7, { paddingLeft: 1, gap: 1, children: [
|
|
1142
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: isFocused ? "green" : "gray", children: "Overview" }),
|
|
1143
|
+
isLoading && /* @__PURE__ */ jsx7(Spinner, { label: "" })
|
|
1144
|
+
] }),
|
|
1145
|
+
/* @__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) => {
|
|
1146
|
+
const globalIndex = scrollOffset + i;
|
|
1147
|
+
const isHighlighted = isFocused && globalIndex === clampedCursor;
|
|
1148
|
+
if (line.type === "spacer") {
|
|
1149
|
+
return /* @__PURE__ */ jsx7(Box7, { height: 1 }, line.key);
|
|
1150
|
+
}
|
|
1151
|
+
if (line.type === "summary") {
|
|
1152
|
+
return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text7, { color: isHighlighted ? "green" : void 0, wrap: "wrap", children: line.text }) }, line.key);
|
|
1153
|
+
}
|
|
1154
|
+
if (line.type === "session") {
|
|
1155
|
+
return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text7, { color: isHighlighted ? "green" : "cyan", children: line.sessionName }) }, line.key);
|
|
1156
|
+
}
|
|
1157
|
+
return /* @__PURE__ */ jsxs5(Box7, { paddingLeft: 3, gap: 1, children: [
|
|
1158
|
+
line.statusLabel ? /* @__PURE__ */ jsxs5(Text7, { color: isHighlighted ? "green" : line.statusColor, children: [
|
|
1159
|
+
"[",
|
|
1160
|
+
line.statusLabel,
|
|
1161
|
+
"]"
|
|
1162
|
+
] }) : /* @__PURE__ */ jsx7(Text7, { color: isHighlighted ? "green" : "gray", children: "[--]" }),
|
|
1163
|
+
/* @__PURE__ */ jsx7(Text7, { color: isHighlighted ? "green" : void 0, wrap: "wrap", children: line.description })
|
|
1164
|
+
] }, line.key);
|
|
1165
|
+
}) })
|
|
1166
|
+
]
|
|
1167
|
+
}
|
|
1168
|
+
);
|
|
1169
|
+
};
|
|
1170
|
+
|
|
886
1171
|
// src/components/ConfirmView.tsx
|
|
887
|
-
import { Box as
|
|
1172
|
+
import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
|
|
888
1173
|
|
|
889
1174
|
// src/hooks/use-terminal-size.ts
|
|
890
1175
|
import { useStdout } from "ink";
|
|
891
|
-
import { useEffect, useState as
|
|
1176
|
+
import { useEffect, useState as useState4 } from "react";
|
|
892
1177
|
var useTerminalSize = () => {
|
|
893
1178
|
const { stdout } = useStdout();
|
|
894
|
-
const [size, setSize] =
|
|
1179
|
+
const [size, setSize] = useState4({
|
|
895
1180
|
rows: stdout.rows ?? 24,
|
|
896
1181
|
columns: stdout.columns ?? 80
|
|
897
1182
|
});
|
|
@@ -911,12 +1196,12 @@ var useTerminalSize = () => {
|
|
|
911
1196
|
};
|
|
912
1197
|
|
|
913
1198
|
// src/components/ConfirmView.tsx
|
|
914
|
-
import { jsx as
|
|
1199
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
915
1200
|
var ConfirmView = ({ selectedDir, prompt, onConfirm, onCancel }) => {
|
|
916
1201
|
const { rows } = useTerminalSize();
|
|
917
1202
|
const previewLines = prompt.split("\n");
|
|
918
1203
|
const maxPreview = Math.min(previewLines.length, rows - 6);
|
|
919
|
-
|
|
1204
|
+
useInput4((_input, key) => {
|
|
920
1205
|
if (key.return) {
|
|
921
1206
|
onConfirm();
|
|
922
1207
|
return;
|
|
@@ -925,30 +1210,30 @@ var ConfirmView = ({ selectedDir, prompt, onConfirm, onCancel }) => {
|
|
|
925
1210
|
onCancel();
|
|
926
1211
|
}
|
|
927
1212
|
});
|
|
928
|
-
return /* @__PURE__ */
|
|
929
|
-
/* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
931
|
-
/* @__PURE__ */
|
|
932
|
-
previewLines.slice(0, maxPreview).map((line, i) => /* @__PURE__ */
|
|
933
|
-
previewLines.length > maxPreview && /* @__PURE__ */
|
|
1213
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", height: rows, children: [
|
|
1214
|
+
/* @__PURE__ */ jsx8(Header, { title: `${APP_TITLE} \u2014 ${selectedDir}` }),
|
|
1215
|
+
/* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { bold: true, children: "New Claude session:" }) }),
|
|
1216
|
+
/* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingLeft: 2, children: [
|
|
1217
|
+
previewLines.slice(0, maxPreview).map((line, i) => /* @__PURE__ */ jsx8(Text8, { color: "white", children: line }, i)),
|
|
1218
|
+
previewLines.length > maxPreview && /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
|
|
934
1219
|
"... (",
|
|
935
1220
|
previewLines.length - maxPreview,
|
|
936
1221
|
" more lines)"
|
|
937
1222
|
] })
|
|
938
1223
|
] }),
|
|
939
|
-
/* @__PURE__ */
|
|
940
|
-
/* @__PURE__ */
|
|
941
|
-
/* @__PURE__ */
|
|
1224
|
+
/* @__PURE__ */ jsxs6(Box8, { gap: 2, children: [
|
|
1225
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Enter confirm" }),
|
|
1226
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Esc cancel" })
|
|
942
1227
|
] })
|
|
943
1228
|
] });
|
|
944
1229
|
};
|
|
945
1230
|
|
|
946
1231
|
// src/components/DeleteSessionView.tsx
|
|
947
|
-
import { Box as
|
|
948
|
-
import { jsx as
|
|
1232
|
+
import { Box as Box9, Text as Text9, useInput as useInput5 } from "ink";
|
|
1233
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
949
1234
|
var DeleteSessionView = ({ sessionName, paneCount, onConfirm, onCancel }) => {
|
|
950
1235
|
const { rows } = useTerminalSize();
|
|
951
|
-
|
|
1236
|
+
useInput5((_input, key) => {
|
|
952
1237
|
if (key.return) {
|
|
953
1238
|
onConfirm();
|
|
954
1239
|
return;
|
|
@@ -957,33 +1242,33 @@ var DeleteSessionView = ({ sessionName, paneCount, onConfirm, onCancel }) => {
|
|
|
957
1242
|
onCancel();
|
|
958
1243
|
}
|
|
959
1244
|
});
|
|
960
|
-
return /* @__PURE__ */
|
|
961
|
-
/* @__PURE__ */
|
|
962
|
-
/* @__PURE__ */
|
|
963
|
-
/* @__PURE__ */
|
|
964
|
-
/* @__PURE__ */
|
|
1245
|
+
return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", height: rows, children: [
|
|
1246
|
+
/* @__PURE__ */ jsx9(Header, { title: APP_TITLE }),
|
|
1247
|
+
/* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", gap: 1, paddingLeft: 2, children: [
|
|
1248
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "red", children: "Delete session?" }),
|
|
1249
|
+
/* @__PURE__ */ jsxs7(Text9, { children: [
|
|
965
1250
|
"Session: ",
|
|
966
|
-
/* @__PURE__ */
|
|
1251
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: sessionName })
|
|
967
1252
|
] }),
|
|
968
|
-
/* @__PURE__ */
|
|
1253
|
+
/* @__PURE__ */ jsxs7(Text9, { children: [
|
|
969
1254
|
"Panes: ",
|
|
970
|
-
/* @__PURE__ */
|
|
1255
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: paneCount })
|
|
971
1256
|
] }),
|
|
972
|
-
/* @__PURE__ */
|
|
1257
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "All processes in this session will be terminated." })
|
|
973
1258
|
] }),
|
|
974
|
-
/* @__PURE__ */
|
|
975
|
-
/* @__PURE__ */
|
|
976
|
-
/* @__PURE__ */
|
|
977
|
-
/* @__PURE__ */
|
|
1259
|
+
/* @__PURE__ */ jsx9(Box9, { flexGrow: 1 }),
|
|
1260
|
+
/* @__PURE__ */ jsxs7(Box9, { gap: 2, children: [
|
|
1261
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter confirm" }),
|
|
1262
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Esc cancel" })
|
|
978
1263
|
] })
|
|
979
1264
|
] });
|
|
980
1265
|
};
|
|
981
1266
|
|
|
982
1267
|
// src/components/DirectorySearchView.tsx
|
|
983
|
-
import { Box as
|
|
984
|
-
import { useMemo as
|
|
1268
|
+
import { Box as Box10, Text as Text10, useInput as useInput6 } from "ink";
|
|
1269
|
+
import { useMemo as useMemo6, useState as useState5 } from "react";
|
|
985
1270
|
import { basename } from "node:path";
|
|
986
|
-
import { jsx as
|
|
1271
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
987
1272
|
var formatPath = (path) => {
|
|
988
1273
|
const home = process.env["HOME"] ?? "";
|
|
989
1274
|
if (home && path.startsWith(home)) {
|
|
@@ -993,9 +1278,9 @@ var formatPath = (path) => {
|
|
|
993
1278
|
};
|
|
994
1279
|
var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
|
|
995
1280
|
const { rows } = useTerminalSize();
|
|
996
|
-
const [query, setQuery] =
|
|
997
|
-
const [cursor, setCursor] =
|
|
998
|
-
const filtered =
|
|
1281
|
+
const [query, setQuery] = useState5("");
|
|
1282
|
+
const [cursor, setCursor] = useState5(0);
|
|
1283
|
+
const filtered = useMemo6(() => {
|
|
999
1284
|
if (!query) return directories;
|
|
1000
1285
|
const lower = query.toLowerCase();
|
|
1001
1286
|
return directories.filter((d) => {
|
|
@@ -1010,11 +1295,11 @@ var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
|
|
|
1010
1295
|
}
|
|
1011
1296
|
const listHeight = rows - 6;
|
|
1012
1297
|
const { scrollOffset, visibleCount } = useScroll(clampedCursor, filtered.length, listHeight);
|
|
1013
|
-
const visibleItems =
|
|
1298
|
+
const visibleItems = useMemo6(
|
|
1014
1299
|
() => filtered.slice(scrollOffset, scrollOffset + visibleCount),
|
|
1015
1300
|
[filtered, scrollOffset, visibleCount]
|
|
1016
1301
|
);
|
|
1017
|
-
|
|
1302
|
+
useInput6((input, key) => {
|
|
1018
1303
|
if (key.escape) {
|
|
1019
1304
|
onCancel();
|
|
1020
1305
|
return;
|
|
@@ -1042,33 +1327,50 @@ var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
|
|
|
1042
1327
|
setCursor(0);
|
|
1043
1328
|
}
|
|
1044
1329
|
});
|
|
1045
|
-
return /* @__PURE__ */
|
|
1046
|
-
/* @__PURE__ */
|
|
1047
|
-
/* @__PURE__ */
|
|
1048
|
-
/* @__PURE__ */
|
|
1049
|
-
/* @__PURE__ */
|
|
1050
|
-
/* @__PURE__ */
|
|
1330
|
+
return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", height: rows, children: [
|
|
1331
|
+
/* @__PURE__ */ jsx10(Header, { title: `${APP_TITLE} \u2014 Add Session` }),
|
|
1332
|
+
/* @__PURE__ */ jsxs8(Box10, { paddingLeft: 1, gap: 1, children: [
|
|
1333
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, children: ">" }),
|
|
1334
|
+
/* @__PURE__ */ jsx10(Text10, { children: query }),
|
|
1335
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: query ? "" : "type to filter..." })
|
|
1051
1336
|
] }),
|
|
1052
|
-
/* @__PURE__ */
|
|
1337
|
+
/* @__PURE__ */ jsx10(Box10, { paddingLeft: 1, children: /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
|
|
1053
1338
|
filtered.length,
|
|
1054
1339
|
"/",
|
|
1055
1340
|
directories.length
|
|
1056
1341
|
] }) }),
|
|
1057
|
-
/* @__PURE__ */
|
|
1342
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleItems.map((dir, i) => {
|
|
1058
1343
|
const globalIndex = scrollOffset + i;
|
|
1059
1344
|
const isHighlighted = globalIndex === clampedCursor;
|
|
1060
|
-
return /* @__PURE__ */
|
|
1061
|
-
/* @__PURE__ */
|
|
1062
|
-
/* @__PURE__ */
|
|
1345
|
+
return /* @__PURE__ */ jsxs8(Box10, { paddingLeft: 1, gap: 1, children: [
|
|
1346
|
+
/* @__PURE__ */ jsx10(Text10, { color: isHighlighted ? "green" : void 0, children: isHighlighted ? "\u25B6" : " " }),
|
|
1347
|
+
/* @__PURE__ */ jsx10(Text10, { color: isHighlighted ? "green" : void 0, bold: isHighlighted, children: formatPath(dir) })
|
|
1063
1348
|
] }, dir);
|
|
1064
1349
|
}) }),
|
|
1065
|
-
/* @__PURE__ */
|
|
1066
|
-
/* @__PURE__ */
|
|
1067
|
-
/* @__PURE__ */
|
|
1350
|
+
/* @__PURE__ */ jsxs8(Box10, { gap: 2, children: [
|
|
1351
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Enter select" }),
|
|
1352
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Esc cancel" })
|
|
1068
1353
|
] })
|
|
1069
1354
|
] });
|
|
1070
1355
|
};
|
|
1071
1356
|
|
|
1357
|
+
// src/hooks/use-interval.ts
|
|
1358
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
1359
|
+
var useInterval = (fn, intervalMs, enabled = true) => {
|
|
1360
|
+
const fnRef = useRef2(fn);
|
|
1361
|
+
fnRef.current = fn;
|
|
1362
|
+
useEffect2(() => {
|
|
1363
|
+
if (!enabled) return;
|
|
1364
|
+
fnRef.current();
|
|
1365
|
+
const timer = setInterval(() => {
|
|
1366
|
+
fnRef.current();
|
|
1367
|
+
}, intervalMs);
|
|
1368
|
+
return () => {
|
|
1369
|
+
clearInterval(timer);
|
|
1370
|
+
};
|
|
1371
|
+
}, [intervalMs, enabled]);
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1072
1374
|
// src/utils/PromiseUtils.ts
|
|
1073
1375
|
var swallow = async (fn) => {
|
|
1074
1376
|
try {
|
|
@@ -1078,7 +1380,7 @@ var swallow = async (fn) => {
|
|
|
1078
1380
|
};
|
|
1079
1381
|
|
|
1080
1382
|
// src/components/ManagerView.tsx
|
|
1081
|
-
import { jsx as
|
|
1383
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1082
1384
|
var MODE = {
|
|
1083
1385
|
split: "split",
|
|
1084
1386
|
confirm: "confirm",
|
|
@@ -1087,9 +1389,11 @@ var MODE = {
|
|
|
1087
1389
|
};
|
|
1088
1390
|
var FOCUS = {
|
|
1089
1391
|
left: "left",
|
|
1090
|
-
right: "right"
|
|
1392
|
+
right: "right",
|
|
1393
|
+
bottom: "bottom"
|
|
1091
1394
|
};
|
|
1092
1395
|
var POLL_INTERVAL = 3e3;
|
|
1396
|
+
var OVERVIEW_POLL_INTERVAL = 6e4;
|
|
1093
1397
|
var ManagerView = ({
|
|
1094
1398
|
actions,
|
|
1095
1399
|
currentSession,
|
|
@@ -1099,16 +1403,22 @@ var ManagerView = ({
|
|
|
1099
1403
|
restoredCwd
|
|
1100
1404
|
}) => {
|
|
1101
1405
|
const { rows, columns } = useTerminalSize();
|
|
1102
|
-
const [sessionsState, setSessionsState] =
|
|
1406
|
+
const [sessionsState, setSessionsState] = useState6({
|
|
1103
1407
|
sessions: [],
|
|
1104
1408
|
isLoading: true
|
|
1105
1409
|
});
|
|
1106
|
-
const [mode, setMode] =
|
|
1107
|
-
const [focus, setFocus] =
|
|
1108
|
-
const [selectedSession, setSelectedSession] =
|
|
1109
|
-
const [pendingPrompt, setPendingPrompt] =
|
|
1110
|
-
const [pendingDeleteSession, setPendingDeleteSession] =
|
|
1111
|
-
const
|
|
1410
|
+
const [mode, setMode] = useState6(restoredPrompt ? MODE.confirm : MODE.split);
|
|
1411
|
+
const [focus, setFocus] = useState6(FOCUS.left);
|
|
1412
|
+
const [selectedSession, setSelectedSession] = useState6(restoredSession);
|
|
1413
|
+
const [pendingPrompt, setPendingPrompt] = useState6(restoredPrompt ?? "");
|
|
1414
|
+
const [pendingDeleteSession, setPendingDeleteSession] = useState6(void 0);
|
|
1415
|
+
const [overviewResult, setOverviewResult] = useState6({
|
|
1416
|
+
overallSummary: "",
|
|
1417
|
+
sessions: []
|
|
1418
|
+
});
|
|
1419
|
+
const [overviewLoading, setOverviewLoading] = useState6(true);
|
|
1420
|
+
const overviewInFlightRef = useRef3(false);
|
|
1421
|
+
const refresh = useCallback4(async () => {
|
|
1112
1422
|
try {
|
|
1113
1423
|
const fetched = await actions.fetchSessions();
|
|
1114
1424
|
setSessionsState((prev) => {
|
|
@@ -1122,31 +1432,52 @@ var ManagerView = ({
|
|
|
1122
1432
|
setSessionsState((prev) => ({ ...prev, isLoading: false }));
|
|
1123
1433
|
}
|
|
1124
1434
|
}, [actions]);
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1435
|
+
useInterval(() => void refresh(), POLL_INTERVAL);
|
|
1436
|
+
useInterval(
|
|
1437
|
+
() => {
|
|
1438
|
+
if (overviewInFlightRef.current) return;
|
|
1439
|
+
overviewInFlightRef.current = true;
|
|
1440
|
+
setOverviewLoading(true);
|
|
1441
|
+
void actions.fetchOverview(sessionsState.sessions).then((result) => {
|
|
1442
|
+
setOverviewResult(result);
|
|
1443
|
+
}).catch(() => {
|
|
1444
|
+
}).finally(() => {
|
|
1445
|
+
setOverviewLoading(false);
|
|
1446
|
+
overviewInFlightRef.current = false;
|
|
1447
|
+
});
|
|
1448
|
+
},
|
|
1449
|
+
OVERVIEW_POLL_INTERVAL,
|
|
1450
|
+
!sessionsState.isLoading
|
|
1451
|
+
);
|
|
1134
1452
|
const resolvedSession = selectedSession ?? sessionsState.sessions[0]?.name;
|
|
1135
|
-
const selectedManagedSession =
|
|
1453
|
+
const selectedManagedSession = useMemo7(
|
|
1136
1454
|
() => sessionsState.sessions.find((s) => s.name === resolvedSession),
|
|
1137
1455
|
[sessionsState.sessions, resolvedSession]
|
|
1138
1456
|
);
|
|
1139
|
-
const selectedGroup =
|
|
1457
|
+
const selectedGroup = useMemo7(() => {
|
|
1140
1458
|
if (!selectedManagedSession) return void 0;
|
|
1141
1459
|
return {
|
|
1142
1460
|
sessionName: selectedManagedSession.name,
|
|
1143
1461
|
tabs: selectedManagedSession.groups.flatMap((g) => g.tabs)
|
|
1144
1462
|
};
|
|
1145
1463
|
}, [selectedManagedSession]);
|
|
1146
|
-
const
|
|
1464
|
+
const allGroups = useMemo7(
|
|
1465
|
+
() => sessionsState.sessions.flatMap((s) => s.groups),
|
|
1466
|
+
[sessionsState.sessions]
|
|
1467
|
+
);
|
|
1468
|
+
const statusCounts = useMemo7(
|
|
1469
|
+
() => allGroups.flatMap((g) => g.tabs).flatMap((t) => t.panes).filter((p) => p.kind === "claude" && p.claudeStatus).reduce((acc, p) => {
|
|
1470
|
+
const s = p.claudeStatus;
|
|
1471
|
+
if (!s) return acc;
|
|
1472
|
+
acc[s] = (acc[s] ?? 0) + 1;
|
|
1473
|
+
return acc;
|
|
1474
|
+
}, {}),
|
|
1475
|
+
[allGroups]
|
|
1476
|
+
);
|
|
1477
|
+
const handleOpenAddSession = useCallback4(() => {
|
|
1147
1478
|
setMode(MODE.addSession);
|
|
1148
1479
|
}, []);
|
|
1149
|
-
const handleAddSessionSelect =
|
|
1480
|
+
const handleAddSessionSelect = useCallback4((path) => {
|
|
1150
1481
|
const name = basename2(path);
|
|
1151
1482
|
setSessionsState((prev) => {
|
|
1152
1483
|
const exists2 = prev.sessions.some((s) => s.name === name);
|
|
@@ -1159,14 +1490,14 @@ var ManagerView = ({
|
|
|
1159
1490
|
setSelectedSession(name);
|
|
1160
1491
|
setMode(MODE.split);
|
|
1161
1492
|
}, []);
|
|
1162
|
-
const handleCancelAddSession =
|
|
1493
|
+
const handleCancelAddSession = useCallback4(() => {
|
|
1163
1494
|
setMode(MODE.split);
|
|
1164
1495
|
}, []);
|
|
1165
|
-
const handleDeleteSession =
|
|
1496
|
+
const handleDeleteSession = useCallback4((name) => {
|
|
1166
1497
|
setPendingDeleteSession(name);
|
|
1167
1498
|
setMode(MODE.deleteSession);
|
|
1168
1499
|
}, []);
|
|
1169
|
-
const handleConfirmDelete =
|
|
1500
|
+
const handleConfirmDelete = useCallback4(() => {
|
|
1170
1501
|
if (!pendingDeleteSession) return;
|
|
1171
1502
|
const session = sessionsState.sessions.find((s) => s.name === pendingDeleteSession);
|
|
1172
1503
|
setSessionsState((prev) => ({
|
|
@@ -1185,11 +1516,11 @@ var ManagerView = ({
|
|
|
1185
1516
|
setPendingDeleteSession(void 0);
|
|
1186
1517
|
setMode(MODE.split);
|
|
1187
1518
|
}, [pendingDeleteSession, resolvedSession, sessionsState.sessions, actions, refresh]);
|
|
1188
|
-
const handleCancelDelete =
|
|
1519
|
+
const handleCancelDelete = useCallback4(() => {
|
|
1189
1520
|
setPendingDeleteSession(void 0);
|
|
1190
1521
|
setMode(MODE.split);
|
|
1191
1522
|
}, []);
|
|
1192
|
-
const handleNewSession =
|
|
1523
|
+
const handleNewSession = useCallback4(
|
|
1193
1524
|
(sessionName) => {
|
|
1194
1525
|
const cwd = selectedManagedSession?.path;
|
|
1195
1526
|
if (!cwd) return;
|
|
@@ -1197,7 +1528,7 @@ var ManagerView = ({
|
|
|
1197
1528
|
},
|
|
1198
1529
|
[actions, selectedManagedSession]
|
|
1199
1530
|
);
|
|
1200
|
-
const handleConfirmNew =
|
|
1531
|
+
const handleConfirmNew = useCallback4(() => {
|
|
1201
1532
|
if (!resolvedSession) return;
|
|
1202
1533
|
const cwd = restoredCwd ?? selectedManagedSession?.path;
|
|
1203
1534
|
if (!cwd) return;
|
|
@@ -1205,53 +1536,53 @@ var ManagerView = ({
|
|
|
1205
1536
|
setPendingPrompt("");
|
|
1206
1537
|
setMode(MODE.split);
|
|
1207
1538
|
}, [resolvedSession, restoredCwd, selectedManagedSession, pendingPrompt, actions, refresh]);
|
|
1208
|
-
const handleCancelConfirm =
|
|
1539
|
+
const handleCancelConfirm = useCallback4(() => {
|
|
1209
1540
|
setPendingPrompt("");
|
|
1210
1541
|
setMode(MODE.split);
|
|
1211
1542
|
}, []);
|
|
1212
|
-
const handleSessionSelect =
|
|
1543
|
+
const handleSessionSelect = useCallback4((name) => {
|
|
1213
1544
|
setSelectedSession(name);
|
|
1214
1545
|
setFocus(FOCUS.right);
|
|
1215
1546
|
}, []);
|
|
1216
|
-
const handleSessionCursorChange =
|
|
1547
|
+
const handleSessionCursorChange = useCallback4((name) => {
|
|
1217
1548
|
setSelectedSession(name);
|
|
1218
1549
|
}, []);
|
|
1219
|
-
const handleNavigate =
|
|
1550
|
+
const handleNavigate = useCallback4(
|
|
1220
1551
|
(up) => {
|
|
1221
1552
|
void actions.navigateToPane(up);
|
|
1222
1553
|
},
|
|
1223
1554
|
[actions]
|
|
1224
1555
|
);
|
|
1225
|
-
const handleBack =
|
|
1556
|
+
const handleBack = useCallback4(() => {
|
|
1226
1557
|
setFocus(FOCUS.left);
|
|
1227
1558
|
}, []);
|
|
1228
|
-
const handleKillPane =
|
|
1559
|
+
const handleKillPane = useCallback4(
|
|
1229
1560
|
async (paneId) => {
|
|
1230
1561
|
await swallow(() => actions.killPane(paneId));
|
|
1231
1562
|
void refresh();
|
|
1232
1563
|
},
|
|
1233
1564
|
[actions, refresh]
|
|
1234
1565
|
);
|
|
1235
|
-
const handleHighlight =
|
|
1566
|
+
const handleHighlight = useCallback4(
|
|
1236
1567
|
async (up) => {
|
|
1237
1568
|
await swallow(() => actions.highlightWindow(up));
|
|
1238
1569
|
},
|
|
1239
1570
|
[actions]
|
|
1240
1571
|
);
|
|
1241
|
-
const handleUnhighlight =
|
|
1572
|
+
const handleUnhighlight = useCallback4(
|
|
1242
1573
|
async (up) => {
|
|
1243
1574
|
await swallow(() => actions.unhighlightWindow(up));
|
|
1244
1575
|
},
|
|
1245
1576
|
[actions]
|
|
1246
1577
|
);
|
|
1247
1578
|
if (sessionsState.isLoading) {
|
|
1248
|
-
return /* @__PURE__ */
|
|
1249
|
-
/* @__PURE__ */
|
|
1250
|
-
/* @__PURE__ */
|
|
1579
|
+
return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: rows, children: [
|
|
1580
|
+
/* @__PURE__ */ jsx11(Header, { title: `${APP_TITLE} v${APP_VERSION}` }),
|
|
1581
|
+
/* @__PURE__ */ jsx11(StatusBar, { message: "Loading...", type: "info" })
|
|
1251
1582
|
] });
|
|
1252
1583
|
}
|
|
1253
1584
|
if (mode === MODE.addSession) {
|
|
1254
|
-
return /* @__PURE__ */
|
|
1585
|
+
return /* @__PURE__ */ jsx11(
|
|
1255
1586
|
DirectorySearchView,
|
|
1256
1587
|
{
|
|
1257
1588
|
directories,
|
|
@@ -1266,7 +1597,7 @@ var ManagerView = ({
|
|
|
1266
1597
|
(sum, g) => sum + g.tabs.reduce((s, t) => s + t.panes.length, 0),
|
|
1267
1598
|
0
|
|
1268
1599
|
) ?? 0;
|
|
1269
|
-
return /* @__PURE__ */
|
|
1600
|
+
return /* @__PURE__ */ jsx11(
|
|
1270
1601
|
DeleteSessionView,
|
|
1271
1602
|
{
|
|
1272
1603
|
sessionName: pendingDeleteSession,
|
|
@@ -1277,7 +1608,7 @@ var ManagerView = ({
|
|
|
1277
1608
|
);
|
|
1278
1609
|
}
|
|
1279
1610
|
if (mode === MODE.confirm && pendingPrompt) {
|
|
1280
|
-
return /* @__PURE__ */
|
|
1611
|
+
return /* @__PURE__ */ jsx11(
|
|
1281
1612
|
ConfirmView,
|
|
1282
1613
|
{
|
|
1283
1614
|
selectedDir: resolvedSession ?? "",
|
|
@@ -1287,26 +1618,29 @@ var ManagerView = ({
|
|
|
1287
1618
|
}
|
|
1288
1619
|
);
|
|
1289
1620
|
}
|
|
1290
|
-
const
|
|
1621
|
+
const fixedRows = 3;
|
|
1622
|
+
const contentHeight = rows - fixedRows;
|
|
1623
|
+
const topHeight = Math.floor(contentHeight / 2);
|
|
1624
|
+
const bottomHeight = contentHeight - topHeight;
|
|
1291
1625
|
const leftWidth = Math.floor(columns / 3);
|
|
1292
1626
|
const rightWidth = columns - leftWidth;
|
|
1293
|
-
return /* @__PURE__ */
|
|
1294
|
-
/* @__PURE__ */
|
|
1295
|
-
/* @__PURE__ */
|
|
1296
|
-
/* @__PURE__ */
|
|
1297
|
-
|
|
1627
|
+
return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: rows, children: [
|
|
1628
|
+
/* @__PURE__ */ jsx11(Header, { title: `${APP_TITLE} - v${APP_VERSION}` }),
|
|
1629
|
+
/* @__PURE__ */ jsxs9(Box11, { flexDirection: "row", height: topHeight, children: [
|
|
1630
|
+
/* @__PURE__ */ jsx11(
|
|
1631
|
+
Box11,
|
|
1298
1632
|
{
|
|
1299
1633
|
flexDirection: "column",
|
|
1300
1634
|
width: leftWidth,
|
|
1301
1635
|
borderStyle: "round",
|
|
1302
1636
|
borderColor: focus === FOCUS.left ? "green" : "gray",
|
|
1303
|
-
children: /* @__PURE__ */
|
|
1637
|
+
children: /* @__PURE__ */ jsx11(
|
|
1304
1638
|
SessionListPanel,
|
|
1305
1639
|
{
|
|
1306
1640
|
sessions: sessionsState.sessions,
|
|
1307
1641
|
currentSession,
|
|
1308
1642
|
isFocused: focus === FOCUS.left,
|
|
1309
|
-
availableRows:
|
|
1643
|
+
availableRows: topHeight - 2,
|
|
1310
1644
|
onSelect: handleSessionSelect,
|
|
1311
1645
|
onCursorChange: handleSessionCursorChange,
|
|
1312
1646
|
onDeleteSession: handleDeleteSession,
|
|
@@ -1315,20 +1649,20 @@ var ManagerView = ({
|
|
|
1315
1649
|
)
|
|
1316
1650
|
}
|
|
1317
1651
|
),
|
|
1318
|
-
/* @__PURE__ */
|
|
1319
|
-
|
|
1652
|
+
/* @__PURE__ */ jsx11(
|
|
1653
|
+
Box11,
|
|
1320
1654
|
{
|
|
1321
1655
|
flexDirection: "column",
|
|
1322
1656
|
width: rightWidth,
|
|
1323
1657
|
borderStyle: "round",
|
|
1324
1658
|
borderColor: focus === FOCUS.right ? "green" : "gray",
|
|
1325
|
-
children: /* @__PURE__ */
|
|
1659
|
+
children: /* @__PURE__ */ jsx11(
|
|
1326
1660
|
PaneListPanel,
|
|
1327
1661
|
{
|
|
1328
1662
|
selectedSession: resolvedSession,
|
|
1329
1663
|
group: selectedGroup,
|
|
1330
1664
|
isFocused: focus === FOCUS.right,
|
|
1331
|
-
availableRows:
|
|
1665
|
+
availableRows: topHeight - 2,
|
|
1332
1666
|
onNavigate: handleNavigate,
|
|
1333
1667
|
onHighlight: handleHighlight,
|
|
1334
1668
|
onUnhighlight: handleUnhighlight,
|
|
@@ -1340,7 +1674,25 @@ var ManagerView = ({
|
|
|
1340
1674
|
}
|
|
1341
1675
|
)
|
|
1342
1676
|
] }),
|
|
1343
|
-
/* @__PURE__ */
|
|
1677
|
+
/* @__PURE__ */ jsx11(
|
|
1678
|
+
SessionOverviewPanel,
|
|
1679
|
+
{
|
|
1680
|
+
overallSummary: overviewResult.overallSummary,
|
|
1681
|
+
items: overviewResult.sessions,
|
|
1682
|
+
groups: allGroups,
|
|
1683
|
+
isLoading: overviewLoading,
|
|
1684
|
+
isFocused: focus === FOCUS.bottom,
|
|
1685
|
+
availableRows: bottomHeight,
|
|
1686
|
+
onBack: handleBack
|
|
1687
|
+
}
|
|
1688
|
+
),
|
|
1689
|
+
/* @__PURE__ */ jsx11(
|
|
1690
|
+
StatusBar,
|
|
1691
|
+
{
|
|
1692
|
+
message: focus === FOCUS.left ? "\u2191/\u2193 move Enter/\u2192 select n add d delete q quit" : focus === FOCUS.right ? "\u2191/\u2193 move Enter focus n new d kill Esc/\u2190 back q quit" : "\u2191/\u2193 scroll Esc/\u2190 back q quit",
|
|
1693
|
+
statusCounts
|
|
1694
|
+
}
|
|
1695
|
+
)
|
|
1344
1696
|
] });
|
|
1345
1697
|
};
|
|
1346
1698
|
|
|
@@ -1380,6 +1732,10 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
|
|
|
1380
1732
|
groups: items.map((item) => item.group)
|
|
1381
1733
|
}));
|
|
1382
1734
|
},
|
|
1735
|
+
fetchOverview: async (sessions) => {
|
|
1736
|
+
const groups = sessions.flatMap((s) => s.groups);
|
|
1737
|
+
return await usecases.manager.fetchOverview(groups);
|
|
1738
|
+
},
|
|
1383
1739
|
createSession: async (sessionName, cwd, prompt) => {
|
|
1384
1740
|
await usecases.manager.createSession({ sessionName, cwd, prompt });
|
|
1385
1741
|
},
|