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