zui-agent-manager 0.1.1 → 0.1.2
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/README.md +1 -13
- package/dist/index.js +169 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,19 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A terminal UI for managing [Claude Code](https://docs.anthropic.com/en/docs/claude-code) agents running in tmux sessions.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
┌─────────────────────── zui - Agent Manager ───────────────────────┐
|
|
7
|
-
│ │
|
|
8
|
-
│ Session Status Running Preview │
|
|
9
|
-
│ ────────────────────────────────────────────────────────────── │
|
|
10
|
-
│> myapp-feat-auth [WORK] 12m 34s Implementing login... │
|
|
11
|
-
│ myapp-fix-api [WAIT] 3m 12s Allow tool use? (y/n) │
|
|
12
|
-
│ myapp-main [IDLE] 1h 5m > │
|
|
13
|
-
│ │
|
|
14
|
-
│ Ent:View g:Git Tab:Pane n:New y:YOLO w:Tree s:Set h:Help k:Kill │
|
|
15
|
-
└────────────────────────────────────────────────────────────────────┘
|
|
16
|
-
```
|
|
17
|
-
|
|
5
|
+
<img width="1728" height="1087" alt="image" src="https://github.com/user-attachments/assets/edfcb7fe-b18f-49a4-a385-e2d76bfb1a8f" />
|
|
18
6
|
|
|
19
7
|
## Features
|
|
20
8
|
|
package/dist/index.js
CHANGED
|
@@ -518,10 +518,64 @@ function showLazygitPane(workdir, rightWidth = 70, lazygitHeight = 40) {
|
|
|
518
518
|
function killBottomRightPane() {
|
|
519
519
|
runCommand("tmux", ["kill-pane", "-t", "{bottom-right}"]);
|
|
520
520
|
}
|
|
521
|
+
function closeRightPane() {
|
|
522
|
+
if (getPaneCount() <= 1) return false;
|
|
523
|
+
const baseIdx = runCommand("tmux", ["show-options", "-gv", "pane-base-index"]);
|
|
524
|
+
const first = /^\d+$/.test(baseIdx) ? baseIdx : "0";
|
|
525
|
+
return runCommandOk("tmux", ["kill-pane", "-a", "-t", `zui-manager:zui.${first}`]);
|
|
526
|
+
}
|
|
527
|
+
function focusBottomRightPane() {
|
|
528
|
+
runCommand("tmux", ["select-pane", "-t", "{bottom-right}"]);
|
|
529
|
+
}
|
|
521
530
|
function killZuiSession() {
|
|
522
531
|
runCommand("tmux", ["kill-session", "-t", "zui-manager"]);
|
|
523
532
|
}
|
|
524
533
|
|
|
534
|
+
// src/ui/keybindings.ts
|
|
535
|
+
var ALT_KEYS = [
|
|
536
|
+
"M-0",
|
|
537
|
+
"M-1",
|
|
538
|
+
"M-2",
|
|
539
|
+
"M-3",
|
|
540
|
+
"M-4",
|
|
541
|
+
"M-5",
|
|
542
|
+
"M-6",
|
|
543
|
+
"M-7",
|
|
544
|
+
"M-8",
|
|
545
|
+
"M-9",
|
|
546
|
+
"M-Enter",
|
|
547
|
+
"M-Tab",
|
|
548
|
+
"M-n",
|
|
549
|
+
"M-y",
|
|
550
|
+
"M-k",
|
|
551
|
+
"M-x",
|
|
552
|
+
"M-g",
|
|
553
|
+
"M-w",
|
|
554
|
+
"M-s",
|
|
555
|
+
"M-h",
|
|
556
|
+
"M-q",
|
|
557
|
+
"M-c"
|
|
558
|
+
];
|
|
559
|
+
function getFirstPaneIndex() {
|
|
560
|
+
const result = runCommand("tmux", ["show-options", "-gv", "pane-base-index"]);
|
|
561
|
+
return /^\d+$/.test(result) ? result : "0";
|
|
562
|
+
}
|
|
563
|
+
function registerAltBindings() {
|
|
564
|
+
const first = getFirstPaneIndex();
|
|
565
|
+
const condition = `[ "#{session_name}" = "zui-manager" ] && [ "#{pane_index}" != "${first}" ]`;
|
|
566
|
+
for (const key of ALT_KEYS) {
|
|
567
|
+
const target = `zui-manager:zui.${first}`;
|
|
568
|
+
const action = key === "M-Tab" ? `select-pane -t ${target}` : `send-keys -t ${target} ${key}`;
|
|
569
|
+
const fallback = `send-keys ${key}`;
|
|
570
|
+
runCommandOk("tmux", ["bind-key", "-n", key, "if-shell", condition, action, fallback]);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function unregisterAltBindings() {
|
|
574
|
+
for (const key of ALT_KEYS) {
|
|
575
|
+
runCommandOk("tmux", ["unbind-key", "-n", key]);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
525
579
|
// src/ui/Header.tsx
|
|
526
580
|
import { Box, Text } from "ink";
|
|
527
581
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -533,7 +587,7 @@ function Header() {
|
|
|
533
587
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
534
588
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
535
589
|
function Footer() {
|
|
536
|
-
return /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", width: "100%", children: /* @__PURE__ */ jsx2(Text2, { backgroundColor: "cyan", color: "black", children: " 1-9:Jump Ent:View g:Git Tab:Pane n:New y:YOLO w:Tree s:Set h:Help k:Kill q:Quit " }) });
|
|
590
|
+
return /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", width: "100%", children: /* @__PURE__ */ jsx2(Text2, { backgroundColor: "cyan", color: "black", children: " 1-9:Jump Ent:View g:Git Tab:Pane n:New y:YOLO w:Tree s:Set h:Help k:Kill q:Quit Alt:Remote " }) });
|
|
537
591
|
}
|
|
538
592
|
|
|
539
593
|
// src/ui/SessionList.tsx
|
|
@@ -781,6 +835,18 @@ var sections = [
|
|
|
781
835
|
["r", "Refresh session list"],
|
|
782
836
|
["h", "This help screen"],
|
|
783
837
|
["q/Esc", "Quit ZUI"]
|
|
838
|
+
] },
|
|
839
|
+
{ title: "Remote Control (from session pane)", items: [
|
|
840
|
+
["Alt+1-9", "Jump to session by index"],
|
|
841
|
+
["Alt+0", "Jump to session 10"],
|
|
842
|
+
["Alt+Enter", "View selected session"],
|
|
843
|
+
["Alt+Tab", "Toggle focus (ZUI \u2194 pane)"],
|
|
844
|
+
["Alt+n/y", "New / YOLO session"],
|
|
845
|
+
["Alt+k", "Kill selected session"],
|
|
846
|
+
["Alt+g", "Show + focus lazygit"],
|
|
847
|
+
["Alt+w", "Create worktree"],
|
|
848
|
+
["Alt+c", "Close right pane"],
|
|
849
|
+
["Alt+q", "Quit ZUI"]
|
|
784
850
|
] }
|
|
785
851
|
];
|
|
786
852
|
function HelpDialog({ onClose }) {
|
|
@@ -865,6 +931,65 @@ function App({ config: initialConfig, initialFocus }) {
|
|
|
865
931
|
}
|
|
866
932
|
}, [sessions, selected]);
|
|
867
933
|
useInput6((input, key) => {
|
|
934
|
+
if (key.meta && key.return && sessions.length > 0) {
|
|
935
|
+
viewSession();
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (key.meta && input.length > 0) {
|
|
939
|
+
if (/^[0-9]$/.test(input) && sessions.length > 0) {
|
|
940
|
+
const idx = input === "0" ? 9 : parseInt(input, 10) - 1;
|
|
941
|
+
if (idx < sessions.length) {
|
|
942
|
+
setSelected(idx);
|
|
943
|
+
const session = sessions[idx];
|
|
944
|
+
if (showSessionInPane(session.name, config)) {
|
|
945
|
+
focusRightPane();
|
|
946
|
+
setStatus(`Showing: ${session.name}`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
if (input === "q") {
|
|
952
|
+
shutdownZui();
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (input === "n") {
|
|
956
|
+
launchSession(false);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (input === "y") {
|
|
960
|
+
launchSession(true);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (input === "k" && sessions.length > 0) {
|
|
964
|
+
killSessionAction();
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (input === "x") {
|
|
968
|
+
cleanupWorktree();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (input === "g" && sessions.length > 0) {
|
|
972
|
+
showLazygitFromRemote();
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
if (input === "w") {
|
|
976
|
+
createWorktreeAction();
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
if (input === "s") {
|
|
980
|
+
setDialog({ type: "settings" });
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
if (input === "h") {
|
|
984
|
+
setDialog({ type: "help" });
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (input === "c") {
|
|
988
|
+
if (closeRightPane()) setStatus("Pane closed");
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
868
993
|
if (dialog.type !== "none") return;
|
|
869
994
|
if (sessions.length > 0 && /^[1-9]$/.test(input)) {
|
|
870
995
|
const idx = parseInt(input, 10) - 1;
|
|
@@ -879,8 +1004,7 @@ function App({ config: initialConfig, initialFocus }) {
|
|
|
879
1004
|
return;
|
|
880
1005
|
}
|
|
881
1006
|
if (input === "q" || key.escape) {
|
|
882
|
-
|
|
883
|
-
exit();
|
|
1007
|
+
shutdownZui();
|
|
884
1008
|
return;
|
|
885
1009
|
}
|
|
886
1010
|
if (input === "r") {
|
|
@@ -906,6 +1030,11 @@ function App({ config: initialConfig, initialFocus }) {
|
|
|
906
1030
|
if (input === "h") setDialog({ type: "help" });
|
|
907
1031
|
if (key.tab) focusRightPane();
|
|
908
1032
|
});
|
|
1033
|
+
function shutdownZui() {
|
|
1034
|
+
unregisterAltBindings();
|
|
1035
|
+
killZuiSession();
|
|
1036
|
+
exit();
|
|
1037
|
+
}
|
|
909
1038
|
function viewSession() {
|
|
910
1039
|
const session = sessions[selected];
|
|
911
1040
|
if (!session) return;
|
|
@@ -916,6 +1045,33 @@ function App({ config: initialConfig, initialFocus }) {
|
|
|
916
1045
|
setStatus("Error: Failed to open pane");
|
|
917
1046
|
}
|
|
918
1047
|
}
|
|
1048
|
+
function showLazygitFromRemote() {
|
|
1049
|
+
const numPanes = getPaneCount();
|
|
1050
|
+
const session = sessions[selected];
|
|
1051
|
+
if (!session) return;
|
|
1052
|
+
if (numPanes >= 3) {
|
|
1053
|
+
focusBottomRightPane();
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const workdir = getSessionWorkdir(session.name, config);
|
|
1057
|
+
if (!workdir) {
|
|
1058
|
+
setStatus("Error: Can't find session workdir");
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (numPanes < 2) {
|
|
1062
|
+
if (showSessionInPane(session.name, config)) {
|
|
1063
|
+
setTimeout(() => {
|
|
1064
|
+
showLazygitPane(workdir, config.layoutRightWidth, config.layoutLazygitHeight);
|
|
1065
|
+
focusBottomRightPane();
|
|
1066
|
+
setStatus(`View + Git: ${session.name}`);
|
|
1067
|
+
}, 200);
|
|
1068
|
+
}
|
|
1069
|
+
} else {
|
|
1070
|
+
showLazygitPane(workdir, config.layoutRightWidth, config.layoutLazygitHeight);
|
|
1071
|
+
focusBottomRightPane();
|
|
1072
|
+
setStatus(`Git: ${workdir.split("/").pop()}`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
919
1075
|
function launchSession(yolo) {
|
|
920
1076
|
const refreshedProjects = discoverProjects(config);
|
|
921
1077
|
setProjects(refreshedProjects);
|
|
@@ -1223,6 +1379,16 @@ function main() {
|
|
|
1223
1379
|
}
|
|
1224
1380
|
const initialFocus = focusArg ? parseInt(focusArg, 10) : void 0;
|
|
1225
1381
|
const config = loadConfig();
|
|
1382
|
+
registerAltBindings();
|
|
1383
|
+
process2.on("exit", () => unregisterAltBindings());
|
|
1384
|
+
process2.on("SIGINT", () => {
|
|
1385
|
+
unregisterAltBindings();
|
|
1386
|
+
process2.exit();
|
|
1387
|
+
});
|
|
1388
|
+
process2.on("SIGTERM", () => {
|
|
1389
|
+
unregisterAltBindings();
|
|
1390
|
+
process2.exit();
|
|
1391
|
+
});
|
|
1226
1392
|
render(React5.createElement(App, { config, initialFocus }));
|
|
1227
1393
|
}
|
|
1228
1394
|
main();
|