@madarco/agentbox 0.1.0 → 0.3.0
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/{chunk-OOOKFFR5.js → chunk-3NCUES35.js} +3 -3
- package/dist/{chunk-RWJE6AER.js → chunk-7NQFIBQG.js} +10 -4
- package/dist/chunk-7NQFIBQG.js.map +1 -0
- package/dist/{chunk-O5HS3QHW.js → chunk-MOC54XL6.js} +67 -16
- package/dist/chunk-MOC54XL6.js.map +1 -0
- package/dist/{create-LSSO7H4I-GWNALUMF.js → create-SE6H4B5U-IWAZHJHV.js} +3 -3
- package/dist/index.js +439 -48
- package/dist/index.js.map +1 -1
- package/dist/{lifecycle-P4FSKGR2-3466P54Y.js → lifecycle-YTMZYKOE-R4M3OR27.js} +3 -3
- package/package.json +1 -1
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +8 -4
- package/runtime/docker/packages/ctl/dist/bin.cjs +24 -3
- package/share/agentbox-setup/SKILL.md +8 -4
- package/dist/chunk-O5HS3QHW.js.map +0 -1
- package/dist/chunk-RWJE6AER.js.map +0 -1
- /package/dist/{chunk-OOOKFFR5.js.map → chunk-3NCUES35.js.map} +0 -0
- /package/dist/{create-LSSO7H4I-GWNALUMF.js.map → create-SE6H4B5U-IWAZHJHV.js.map} +0 -0
- /package/dist/{lifecycle-P4FSKGR2-3466P54Y.js.map → lifecycle-YTMZYKOE-R4M3OR27.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createBox
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3NCUES35.js";
|
|
5
5
|
import {
|
|
6
6
|
AmbiguousBoxError,
|
|
7
7
|
BoxNotFoundError,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
startBox,
|
|
16
16
|
stopBox,
|
|
17
17
|
unpauseBox
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-7NQFIBQG.js";
|
|
19
19
|
import {
|
|
20
20
|
ClaudeSessionError,
|
|
21
21
|
SHARED_CLAUDE_VOLUME,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
resolveClaudeVolume,
|
|
38
38
|
startClaudeSession,
|
|
39
39
|
stopRelay
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-MOC54XL6.js";
|
|
41
41
|
import {
|
|
42
42
|
STATE_DIR,
|
|
43
43
|
readState,
|
|
@@ -1600,6 +1600,7 @@ var InputParser = class {
|
|
|
1600
1600
|
if (b === LEADER) {
|
|
1601
1601
|
this.flush();
|
|
1602
1602
|
this.state = "leader";
|
|
1603
|
+
this.onEvent({ type: "leader", active: true });
|
|
1603
1604
|
this.arm(this.leaderMs, "leader");
|
|
1604
1605
|
} else if (b === ESC) {
|
|
1605
1606
|
this.flush();
|
|
@@ -1622,14 +1623,18 @@ var InputParser = class {
|
|
|
1622
1623
|
if (c === "v") this.onEvent({ type: "action", name: "vnc" });
|
|
1623
1624
|
else if (c === "w") this.onEvent({ type: "action", name: "web" });
|
|
1624
1625
|
else if (c === "c") this.onEvent({ type: "action", name: "code" });
|
|
1625
|
-
else if (c === "
|
|
1626
|
-
else if (c === "
|
|
1626
|
+
else if (c === "s") this.onEvent({ type: "action", name: "stop" });
|
|
1627
|
+
else if (c === "p") this.onEvent({ type: "action", name: "pause" });
|
|
1628
|
+
else if (c === "d") this.onEvent({ type: "action", name: "destroy" });
|
|
1629
|
+
else if (c === "q") this.onEvent({ type: "quit" });
|
|
1630
|
+
else if (c === "k") this.onEvent({ type: "switch", dir: "prev" });
|
|
1627
1631
|
else if (c === "j" || c === "n" || c === "N") this.onEvent({ type: "switch", dir: "next" });
|
|
1628
1632
|
else {
|
|
1629
1633
|
this.fwd.push(b);
|
|
1630
1634
|
this.flush();
|
|
1631
1635
|
}
|
|
1632
1636
|
}
|
|
1637
|
+
this.onEvent({ type: "leader", active: false });
|
|
1633
1638
|
this.state = "normal";
|
|
1634
1639
|
i++;
|
|
1635
1640
|
continue;
|
|
@@ -1737,6 +1742,7 @@ var InputParser = class {
|
|
|
1737
1742
|
if (kind === "leader" && this.state === "leader") {
|
|
1738
1743
|
this.fwd.push(LEADER);
|
|
1739
1744
|
this.flush();
|
|
1745
|
+
this.onEvent({ type: "leader", active: false });
|
|
1740
1746
|
this.state = "normal";
|
|
1741
1747
|
} else if (kind === "esc" && (this.state === "esc" || this.state === "mouseX10")) {
|
|
1742
1748
|
this.forwardVerbatim(this.esc);
|
|
@@ -1861,6 +1867,18 @@ var PtySession = class {
|
|
|
1861
1867
|
};
|
|
1862
1868
|
|
|
1863
1869
|
// src/dashboard/sidebar.ts
|
|
1870
|
+
function ellipsize(s, max) {
|
|
1871
|
+
if (max <= 0) return "";
|
|
1872
|
+
if (s.length <= max) return s;
|
|
1873
|
+
if (max === 1) return "\u2026";
|
|
1874
|
+
return s.slice(0, max - 1) + "\u2026";
|
|
1875
|
+
}
|
|
1876
|
+
function ellipsizeHead(s, max) {
|
|
1877
|
+
if (max <= 0) return "";
|
|
1878
|
+
if (s.length <= max) return s;
|
|
1879
|
+
if (max === 1) return "\u2026";
|
|
1880
|
+
return "\u2026" + s.slice(s.length - (max - 1));
|
|
1881
|
+
}
|
|
1864
1882
|
function activityCell(b) {
|
|
1865
1883
|
if (b.state !== "running") return `[${b.state}]`;
|
|
1866
1884
|
switch (b.claudeActivity) {
|
|
@@ -1876,8 +1894,12 @@ function activityCell(b) {
|
|
|
1876
1894
|
}
|
|
1877
1895
|
var NEW_BOX_ID = "__agentbox_new__";
|
|
1878
1896
|
var NEW_BOX_LABEL = "+ New box";
|
|
1879
|
-
var SIDEBAR_HEADER = "
|
|
1880
|
-
|
|
1897
|
+
var SIDEBAR_HEADER = "AgentBox";
|
|
1898
|
+
function topBorder(label, w) {
|
|
1899
|
+
const lead = `\u256D\u2500\u2500\u2500 ${label} `;
|
|
1900
|
+
if (lead.length >= w) return lead.slice(0, w);
|
|
1901
|
+
return lead + "\u2500".repeat(w - lead.length);
|
|
1902
|
+
}
|
|
1881
1903
|
function fit(s, w) {
|
|
1882
1904
|
if (s.length === w) return s;
|
|
1883
1905
|
if (s.length > w) return s.slice(0, w);
|
|
@@ -1889,17 +1911,55 @@ function center(s, w) {
|
|
|
1889
1911
|
const leftPad = Math.floor(pad / 2);
|
|
1890
1912
|
return " ".repeat(leftPad) + s + " ".repeat(pad - leftPad);
|
|
1891
1913
|
}
|
|
1914
|
+
function projectLabel(project) {
|
|
1915
|
+
if (!project) return "(no project)";
|
|
1916
|
+
const parts = project.split("/").filter(Boolean);
|
|
1917
|
+
return parts[parts.length - 1] ?? project;
|
|
1918
|
+
}
|
|
1919
|
+
function stripTitleGlyph(s) {
|
|
1920
|
+
const t = s.replace(/^[\s\p{S}*·]+/u, "");
|
|
1921
|
+
return t.length > 0 ? t : s.trim();
|
|
1922
|
+
}
|
|
1923
|
+
function boxRow(b, marker, w) {
|
|
1924
|
+
const numStr = b.index != null ? `${b.index} ` : "";
|
|
1925
|
+
const status = activityCell(b);
|
|
1926
|
+
const left = `${marker}${numStr}`;
|
|
1927
|
+
const room = w - left.length - status.length - 1;
|
|
1928
|
+
if (room <= 0) return fit(`${left}${status}`, w);
|
|
1929
|
+
const middle = b.state === "running" && b.sessionTitle ? ellipsize(stripTitleGlyph(b.sessionTitle), room) : ellipsizeHead(b.name, room);
|
|
1930
|
+
return fit(`${left}${middle}`, w - status.length) + status;
|
|
1931
|
+
}
|
|
1892
1932
|
function sidebarLines(boxes, selectedId, w, h) {
|
|
1893
|
-
const lines = [
|
|
1894
|
-
const
|
|
1933
|
+
const lines = [topBorder(SIDEBAR_HEADER, w), fit("", w)];
|
|
1934
|
+
const rowOwner = [null, null];
|
|
1935
|
+
const headerRows = [true, false];
|
|
1936
|
+
const push = (line, owner, header) => {
|
|
1937
|
+
lines.push(fit(line, w));
|
|
1938
|
+
rowOwner.push(owner);
|
|
1939
|
+
headerRows.push(header);
|
|
1940
|
+
};
|
|
1941
|
+
let prevProject;
|
|
1942
|
+
let seenGroup = false;
|
|
1895
1943
|
for (const b of boxes) {
|
|
1896
|
-
const marker = b.id === selectedId ? "\u25B8
|
|
1897
|
-
|
|
1898
|
-
|
|
1944
|
+
const marker = b.id === selectedId ? "\u25B8" : " ";
|
|
1945
|
+
if (b.id === NEW_BOX_ID) {
|
|
1946
|
+
push(`${marker}${NEW_BOX_LABEL}`, b.id, false);
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
if (!seenGroup || b.project !== prevProject) {
|
|
1950
|
+
push(center(` \u2500\u2500 ${projectLabel(b.project)} \u2500\u2500 `, w), null, true);
|
|
1951
|
+
prevProject = b.project;
|
|
1952
|
+
seenGroup = true;
|
|
1953
|
+
}
|
|
1954
|
+
push(boxRow(b, marker, w), b.id, false);
|
|
1899
1955
|
}
|
|
1900
|
-
if (boxes.length === 0)
|
|
1901
|
-
while (lines.length < h)
|
|
1902
|
-
return
|
|
1956
|
+
if (boxes.length === 0) push(" (no boxes)", null, false);
|
|
1957
|
+
while (lines.length < h) push("", null, false);
|
|
1958
|
+
return {
|
|
1959
|
+
lines: lines.slice(0, h),
|
|
1960
|
+
rowOwner: rowOwner.slice(0, h),
|
|
1961
|
+
headerRows: headerRows.slice(0, h)
|
|
1962
|
+
};
|
|
1903
1963
|
}
|
|
1904
1964
|
function menuLines(boxName, w, h) {
|
|
1905
1965
|
const body = [
|
|
@@ -1916,6 +1976,28 @@ function menuLines(boxName, w, h) {
|
|
|
1916
1976
|
for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? "", w));
|
|
1917
1977
|
return out;
|
|
1918
1978
|
}
|
|
1979
|
+
function lifecycleMenuLines(boxName, state, confirmDestroy, w, h) {
|
|
1980
|
+
const body = confirmDestroy ? [
|
|
1981
|
+
"",
|
|
1982
|
+
` Destroy ${boxName}?`,
|
|
1983
|
+
" This removes the container and its volumes.",
|
|
1984
|
+
"",
|
|
1985
|
+
" [y] Yes, destroy",
|
|
1986
|
+
" [any other key] Cancel"
|
|
1987
|
+
] : [
|
|
1988
|
+
"",
|
|
1989
|
+
` Box ${boxName} is ${state}.`,
|
|
1990
|
+
"",
|
|
1991
|
+
state === "paused" ? " [u] Unpause" : " [s] Start",
|
|
1992
|
+
" [d] Destroy",
|
|
1993
|
+
"",
|
|
1994
|
+
" Ctrl+Option+\u2191/\u2193 switch \xB7 Ctrl-a then q quit"
|
|
1995
|
+
];
|
|
1996
|
+
const top = Math.max(0, Math.floor((h - body.length) / 2));
|
|
1997
|
+
const out = [];
|
|
1998
|
+
for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? "", w));
|
|
1999
|
+
return out;
|
|
2000
|
+
}
|
|
1919
2001
|
function createMenuLines(where, w, h) {
|
|
1920
2002
|
const body = [
|
|
1921
2003
|
"",
|
|
@@ -1941,29 +2023,58 @@ var BRAND_NOBOLD = "\x1B[22m";
|
|
|
1941
2023
|
var HINT_KEY = "\x1B[38;5;255m";
|
|
1942
2024
|
var HINT_TXT = "\x1B[38;5;245m";
|
|
1943
2025
|
var BAR_RESET = "\x1B[0m";
|
|
2026
|
+
var SWITCH_HINT = ["Control+Option+\u2191/\u2193", "switch"];
|
|
1944
2027
|
var HINT_GROUPS = [
|
|
1945
|
-
|
|
2028
|
+
SWITCH_HINT,
|
|
1946
2029
|
["Control+a c", "code"],
|
|
1947
2030
|
["Control+a v", "vnc"],
|
|
1948
2031
|
["Control+a w", "web"],
|
|
1949
2032
|
["Control+a q", "quit"]
|
|
1950
2033
|
];
|
|
1951
|
-
|
|
2034
|
+
var COLLAPSED_HINT_GROUPS = [
|
|
2035
|
+
SWITCH_HINT,
|
|
2036
|
+
["Control+a", "more"]
|
|
2037
|
+
];
|
|
2038
|
+
var ADVANCED_HINT_GROUPS = [
|
|
2039
|
+
["c", "code"],
|
|
2040
|
+
["v", "vnc"],
|
|
2041
|
+
["w", "web"],
|
|
2042
|
+
["s", "stop"],
|
|
2043
|
+
["p", "pause"],
|
|
2044
|
+
["d", "destroy"],
|
|
2045
|
+
["q", "quit"]
|
|
2046
|
+
];
|
|
2047
|
+
function statusLine(box, w, stateLabel, groups = HINT_GROUPS) {
|
|
1952
2048
|
const state = stateLabel ?? (box ? box.state === "running" ? box.claudeActivity ?? "unknown" : box.state : "");
|
|
1953
2049
|
const brandPrefix = box ? " agentbox \u25B8 " : " agentbox ";
|
|
1954
|
-
const
|
|
1955
|
-
const
|
|
1956
|
-
const
|
|
2050
|
+
const base = box ? `${box.name} (${state})` : "";
|
|
2051
|
+
const coreMain = box ? `${base} ` : "";
|
|
2052
|
+
const corePlain = brandPrefix + coreMain;
|
|
1957
2053
|
const SEP = " \u2502 ";
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
`${HINT_TXT}${SEP}`
|
|
1961
|
-
)
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2054
|
+
const renderHints = (g) => ({
|
|
2055
|
+
plain: g.map(([k, l]) => `${k}: ${l}`).join(SEP) + " ",
|
|
2056
|
+
styled: g.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(`${HINT_TXT}${SEP}`) + " "
|
|
2057
|
+
});
|
|
2058
|
+
let hints = null;
|
|
2059
|
+
for (const g of [groups, COLLAPSED_HINT_GROUPS]) {
|
|
2060
|
+
const h = renderHints(g);
|
|
2061
|
+
if (corePlain.length + h.plain.length + 1 <= w) {
|
|
2062
|
+
hints = h;
|
|
2063
|
+
break;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
if (!hints) {
|
|
2067
|
+
return BAR_BASE + BAR_BRAND + fit(corePlain, w) + BAR_RESET;
|
|
2068
|
+
}
|
|
2069
|
+
const room = w - corePlain.length - hints.plain.length - 1;
|
|
2070
|
+
let titleSeg = "";
|
|
2071
|
+
if (box?.sessionTitle && room >= 7) {
|
|
2072
|
+
titleSeg = ` \u2014 ${ellipsize(box.sessionTitle, Math.min(40, room - 3))}`;
|
|
2073
|
+
}
|
|
2074
|
+
const leftPlain = brandPrefix + base + titleSeg + (box ? " " : "");
|
|
2075
|
+
const leftStyled = BAR_BRAND + brandPrefix + BRAND_BOLD + base + titleSeg + (box ? " " : "") + BRAND_NOBOLD;
|
|
2076
|
+
const gap = w - leftPlain.length - hints.plain.length;
|
|
2077
|
+
return BAR_BASE + leftStyled + BAR_BASE + " ".repeat(gap) + hints.styled + BAR_RESET;
|
|
1967
2078
|
}
|
|
1968
2079
|
|
|
1969
2080
|
// src/dashboard/compositor.ts
|
|
@@ -1974,6 +2085,7 @@ var SGR_RESET = "\x1B[0m";
|
|
|
1974
2085
|
var POLL_MS = 1e3;
|
|
1975
2086
|
var FRAME_MS = 16;
|
|
1976
2087
|
var RESIZE_DEBOUNCE_MS = 120;
|
|
2088
|
+
var LEADER_LINGER_MS = 1500;
|
|
1977
2089
|
var SYNC_BEGIN = "\x1B[?2026h";
|
|
1978
2090
|
var SYNC_END = "\x1B[?2026l";
|
|
1979
2091
|
function cursorTo2(x, y) {
|
|
@@ -1986,10 +2098,41 @@ var Compositor = class {
|
|
|
1986
2098
|
this.layout = computeLayout(this.out.columns ?? 100, this.out.rows ?? 30);
|
|
1987
2099
|
this.parser = new InputParser({
|
|
1988
2100
|
onEvent: (e) => {
|
|
2101
|
+
if (e.type === "leader") {
|
|
2102
|
+
if (this.leaderLingerTimer) {
|
|
2103
|
+
clearTimeout(this.leaderLingerTimer);
|
|
2104
|
+
this.leaderLingerTimer = null;
|
|
2105
|
+
}
|
|
2106
|
+
if (e.active) {
|
|
2107
|
+
this.leaderActive = true;
|
|
2108
|
+
} else {
|
|
2109
|
+
this.leaderLingerTimer = setTimeout(() => {
|
|
2110
|
+
this.leaderLingerTimer = null;
|
|
2111
|
+
this.leaderActive = false;
|
|
2112
|
+
this.drawChrome();
|
|
2113
|
+
}, LEADER_LINGER_MS);
|
|
2114
|
+
}
|
|
2115
|
+
this.drawChrome();
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
if (this.pendingConfirm) {
|
|
2119
|
+
if (e.type === "forward") {
|
|
2120
|
+
this.handleConfirmKey(e.bytes);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
this.pendingConfirm = null;
|
|
2124
|
+
this.drawChrome();
|
|
2125
|
+
}
|
|
1989
2126
|
if (e.type === "quit") this.onSig();
|
|
1990
2127
|
else if (e.type === "switch") this.switchBox(e.dir);
|
|
1991
|
-
else if (e.type === "action")
|
|
1992
|
-
|
|
2128
|
+
else if (e.type === "action") {
|
|
2129
|
+
if (e.name === "pause" || e.name === "stop" || e.name === "destroy") {
|
|
2130
|
+
void this.doLifecycle(e.name);
|
|
2131
|
+
} else {
|
|
2132
|
+
void this.doAction(e.name);
|
|
2133
|
+
}
|
|
2134
|
+
} else if (this.createMenu) this.handleCreateMenuKey(e.bytes);
|
|
2135
|
+
else if (this.lifecycleMenu) this.handleLifecycleMenuKey(e.bytes);
|
|
1993
2136
|
else if (this.menu) this.handleMenuKey(e.bytes);
|
|
1994
2137
|
else this.session?.write(e.bytes);
|
|
1995
2138
|
},
|
|
@@ -2013,7 +2156,16 @@ var Compositor = class {
|
|
|
2013
2156
|
session = null;
|
|
2014
2157
|
placeholder = null;
|
|
2015
2158
|
menu = null;
|
|
2159
|
+
lifecycleMenu = null;
|
|
2016
2160
|
createMenu = null;
|
|
2161
|
+
/** True while the Ctrl-a leader is pending — swaps the footer to the
|
|
2162
|
+
* expanded chord menu (chrome only; never touches the right pane). */
|
|
2163
|
+
leaderActive = false;
|
|
2164
|
+
/** Holds the expanded footer for LEADER_LINGER_MS after the leader resolves
|
|
2165
|
+
* (so the chord menu doesn't flash by). */
|
|
2166
|
+
leaderLingerTimer = null;
|
|
2167
|
+
/** Set while a destroy confirm is pending in the status bar. */
|
|
2168
|
+
pendingConfirm = null;
|
|
2017
2169
|
activeMode = "claude";
|
|
2018
2170
|
flashMsg = null;
|
|
2019
2171
|
flashTimer = null;
|
|
@@ -2073,7 +2225,9 @@ var Compositor = class {
|
|
|
2073
2225
|
return this.boxes.find((b) => b.id === this.selectedId);
|
|
2074
2226
|
}
|
|
2075
2227
|
async poll() {
|
|
2076
|
-
const before = JSON.stringify(
|
|
2228
|
+
const before = JSON.stringify(
|
|
2229
|
+
this.boxes.map((b) => [b.id, b.state, b.claudeActivity, b.sessionTitle])
|
|
2230
|
+
);
|
|
2077
2231
|
await this.refreshBoxes();
|
|
2078
2232
|
if (this.busy) {
|
|
2079
2233
|
} else if (!this.boxes.some((b) => b.id === this.selectedId) && this.boxes[0]) {
|
|
@@ -2082,10 +2236,12 @@ var Compositor = class {
|
|
|
2082
2236
|
} else {
|
|
2083
2237
|
const box = this.selectedBox();
|
|
2084
2238
|
const running = box?.state === "running";
|
|
2085
|
-
const reresolve = this.session && !running || this.placeholder && running || this.menu && !running;
|
|
2239
|
+
const reresolve = this.session && !running || this.placeholder && running || this.menu && !running || this.lifecycleMenu != null && box?.state !== this.lifecycleMenu.state;
|
|
2086
2240
|
if (reresolve) await this.spawnActive();
|
|
2087
2241
|
}
|
|
2088
|
-
if (JSON.stringify(
|
|
2242
|
+
if (JSON.stringify(
|
|
2243
|
+
this.boxes.map((b) => [b.id, b.state, b.claudeActivity, b.sessionTitle])
|
|
2244
|
+
) !== before) {
|
|
2089
2245
|
this.drawChrome();
|
|
2090
2246
|
}
|
|
2091
2247
|
}
|
|
@@ -2098,7 +2254,9 @@ var Compositor = class {
|
|
|
2098
2254
|
this.disposeSession();
|
|
2099
2255
|
this.placeholder = null;
|
|
2100
2256
|
this.menu = null;
|
|
2257
|
+
this.lifecycleMenu = null;
|
|
2101
2258
|
this.createMenu = null;
|
|
2259
|
+
this.pendingConfirm = null;
|
|
2102
2260
|
this.clearRightPane();
|
|
2103
2261
|
const id = this.selectedId;
|
|
2104
2262
|
const target = await this.deps.resolveTarget(id);
|
|
@@ -2110,7 +2268,9 @@ var Compositor = class {
|
|
|
2110
2268
|
this.disposeSession();
|
|
2111
2269
|
this.placeholder = null;
|
|
2112
2270
|
this.menu = null;
|
|
2271
|
+
this.lifecycleMenu = null;
|
|
2113
2272
|
this.createMenu = null;
|
|
2273
|
+
this.pendingConfirm = null;
|
|
2114
2274
|
if (target.kind === "attach") {
|
|
2115
2275
|
this.activeMode = target.mode ?? "claude";
|
|
2116
2276
|
this.session = new PtySession(
|
|
@@ -2124,6 +2284,12 @@ var Compositor = class {
|
|
|
2124
2284
|
);
|
|
2125
2285
|
} else if (target.kind === "menu") {
|
|
2126
2286
|
this.menu = { boxName: this.selectedBox()?.name ?? this.selectedId };
|
|
2287
|
+
} else if (target.kind === "lifecycle-menu") {
|
|
2288
|
+
this.lifecycleMenu = {
|
|
2289
|
+
boxName: this.selectedBox()?.name ?? this.selectedId,
|
|
2290
|
+
state: target.state,
|
|
2291
|
+
confirmDestroy: false
|
|
2292
|
+
};
|
|
2127
2293
|
} else if (target.kind === "create-menu") {
|
|
2128
2294
|
this.createMenu = { where: target.where };
|
|
2129
2295
|
} else {
|
|
@@ -2176,6 +2342,163 @@ var Compositor = class {
|
|
|
2176
2342
|
this.busy = false;
|
|
2177
2343
|
}
|
|
2178
2344
|
}
|
|
2345
|
+
handleLifecycleMenuKey(bytes) {
|
|
2346
|
+
const m = this.lifecycleMenu;
|
|
2347
|
+
if (!m) return;
|
|
2348
|
+
for (const b of bytes) {
|
|
2349
|
+
if (m.confirmDestroy) {
|
|
2350
|
+
if (b === 121 || b === 13 || b === 10) {
|
|
2351
|
+
void this.runDestroy(this.selectedId, this.selectedBox()?.name ?? this.selectedId);
|
|
2352
|
+
} else {
|
|
2353
|
+
m.confirmDestroy = false;
|
|
2354
|
+
this.drawChrome();
|
|
2355
|
+
this.scheduleRender();
|
|
2356
|
+
}
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
const resumeKey = m.state === "paused" ? 117 : 115;
|
|
2360
|
+
if (b === resumeKey) {
|
|
2361
|
+
void this.resumeSelected();
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
if (b === 100) {
|
|
2365
|
+
m.confirmDestroy = true;
|
|
2366
|
+
this.drawChrome();
|
|
2367
|
+
this.scheduleRender();
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
async resumeSelected() {
|
|
2373
|
+
if (this.busy) return;
|
|
2374
|
+
const id = this.selectedId;
|
|
2375
|
+
const name = this.selectedBox()?.name ?? id;
|
|
2376
|
+
const verb = this.lifecycleMenu?.state === "stopped" ? "start" : "unpause";
|
|
2377
|
+
this.busy = true;
|
|
2378
|
+
this.menu = null;
|
|
2379
|
+
this.lifecycleMenu = null;
|
|
2380
|
+
this.createMenu = null;
|
|
2381
|
+
this.placeholder = ["", " Resuming\u2026"];
|
|
2382
|
+
this.prevRows = null;
|
|
2383
|
+
this.drawChrome();
|
|
2384
|
+
this.scheduleRender();
|
|
2385
|
+
try {
|
|
2386
|
+
await this.deps.resumeBox(id);
|
|
2387
|
+
if (this.selectedId !== id || this.tornDown) return;
|
|
2388
|
+
await this.refreshBoxes();
|
|
2389
|
+
await this.spawnActive();
|
|
2390
|
+
} catch (err) {
|
|
2391
|
+
if (this.selectedId !== id || this.tornDown) return;
|
|
2392
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2393
|
+
this.placeholder = [
|
|
2394
|
+
"",
|
|
2395
|
+
` Failed to ${verb} ${name}:`,
|
|
2396
|
+
` ${msg}`,
|
|
2397
|
+
"",
|
|
2398
|
+
` Try from a shell: agentbox ${verb} ${name}`
|
|
2399
|
+
];
|
|
2400
|
+
this.prevRows = null;
|
|
2401
|
+
this.scheduleRender();
|
|
2402
|
+
} finally {
|
|
2403
|
+
this.busy = false;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
/** Destroy `id` and recover the selection. Shared by the lifecycle-menu
|
|
2407
|
+
* confirm and the running-box `Ctrl-a d` status-bar confirm. */
|
|
2408
|
+
async runDestroy(id, name) {
|
|
2409
|
+
if (this.busy) return;
|
|
2410
|
+
this.busy = true;
|
|
2411
|
+
this.menu = null;
|
|
2412
|
+
this.lifecycleMenu = null;
|
|
2413
|
+
this.createMenu = null;
|
|
2414
|
+
this.placeholder = ["", " Destroying\u2026"];
|
|
2415
|
+
this.prevRows = null;
|
|
2416
|
+
this.drawChrome();
|
|
2417
|
+
this.scheduleRender();
|
|
2418
|
+
try {
|
|
2419
|
+
await this.deps.destroyBox(id);
|
|
2420
|
+
if (this.tornDown) return;
|
|
2421
|
+
await this.refreshBoxes();
|
|
2422
|
+
if (this.boxes[0]) this.selectedId = this.boxes[0].id;
|
|
2423
|
+
await this.spawnActive();
|
|
2424
|
+
this.flash(`destroyed ${name}`);
|
|
2425
|
+
} catch (err) {
|
|
2426
|
+
if (this.tornDown) return;
|
|
2427
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2428
|
+
this.placeholder = [
|
|
2429
|
+
"",
|
|
2430
|
+
` Failed to destroy ${name}:`,
|
|
2431
|
+
` ${msg}`,
|
|
2432
|
+
"",
|
|
2433
|
+
` Try from a shell: agentbox destroy ${name}`
|
|
2434
|
+
];
|
|
2435
|
+
this.prevRows = null;
|
|
2436
|
+
this.scheduleRender();
|
|
2437
|
+
} finally {
|
|
2438
|
+
this.busy = false;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
/** Ctrl-a p/s/d on the selected box. pause/stop transition state (the pane
|
|
2442
|
+
* re-resolves to the lifecycle menu); destroy asks to confirm first. */
|
|
2443
|
+
async doLifecycle(name) {
|
|
2444
|
+
if (this.selectedId === NEW_BOX_ID) {
|
|
2445
|
+
this.flash("select a box first");
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
const id = this.selectedId;
|
|
2449
|
+
const boxName = this.selectedBox()?.name ?? id;
|
|
2450
|
+
if (name === "destroy") {
|
|
2451
|
+
this.pendingConfirm = { boxId: id, name: boxName };
|
|
2452
|
+
this.drawChrome();
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
if (this.selectedBox()?.state !== "running") {
|
|
2456
|
+
this.flash(`${boxName} is not running`);
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
if (this.busy) return;
|
|
2460
|
+
this.busy = true;
|
|
2461
|
+
this.menu = null;
|
|
2462
|
+
this.lifecycleMenu = null;
|
|
2463
|
+
this.createMenu = null;
|
|
2464
|
+
this.placeholder = ["", name === "pause" ? " Pausing\u2026" : " Stopping\u2026"];
|
|
2465
|
+
this.prevRows = null;
|
|
2466
|
+
this.drawChrome();
|
|
2467
|
+
this.scheduleRender();
|
|
2468
|
+
try {
|
|
2469
|
+
if (name === "pause") await this.deps.pauseBox(id);
|
|
2470
|
+
else await this.deps.stopBox(id);
|
|
2471
|
+
if (this.selectedId !== id || this.tornDown) return;
|
|
2472
|
+
await this.refreshBoxes();
|
|
2473
|
+
await this.spawnActive();
|
|
2474
|
+
this.flash(`${name === "pause" ? "paused" : "stopped"} ${boxName}`);
|
|
2475
|
+
} catch (err) {
|
|
2476
|
+
if (this.selectedId !== id || this.tornDown) return;
|
|
2477
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2478
|
+
this.placeholder = [
|
|
2479
|
+
"",
|
|
2480
|
+
` Failed to ${name} ${boxName}:`,
|
|
2481
|
+
` ${msg}`,
|
|
2482
|
+
"",
|
|
2483
|
+
` Try from a shell: agentbox ${name} ${boxName}`
|
|
2484
|
+
];
|
|
2485
|
+
this.prevRows = null;
|
|
2486
|
+
this.scheduleRender();
|
|
2487
|
+
} finally {
|
|
2488
|
+
this.busy = false;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
handleConfirmKey(bytes) {
|
|
2492
|
+
const c = this.pendingConfirm;
|
|
2493
|
+
if (!c) return;
|
|
2494
|
+
const b = bytes[0];
|
|
2495
|
+
this.pendingConfirm = null;
|
|
2496
|
+
if (b === 121 || b === 13 || b === 10) {
|
|
2497
|
+
void this.runDestroy(c.boxId, c.name);
|
|
2498
|
+
} else {
|
|
2499
|
+
this.drawChrome();
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2179
2502
|
handleCreateMenuKey(bytes) {
|
|
2180
2503
|
for (const b of bytes) {
|
|
2181
2504
|
if (b === 99 || b === 13 || b === 10) {
|
|
@@ -2257,6 +2580,7 @@ var Compositor = class {
|
|
|
2257
2580
|
}
|
|
2258
2581
|
switchBox(dir) {
|
|
2259
2582
|
if (this.boxes.length === 0) return;
|
|
2583
|
+
this.pendingConfirm = null;
|
|
2260
2584
|
const i = Math.max(
|
|
2261
2585
|
0,
|
|
2262
2586
|
this.boxes.findIndex((b) => b.id === this.selectedId)
|
|
@@ -2300,6 +2624,17 @@ var Compositor = class {
|
|
|
2300
2624
|
let s = SYNC_BEGIN + "\x1B[?25l";
|
|
2301
2625
|
for (let i = 0; i < r.h; i++) s += cursorTo2(r.x, r.y + i) + "\x1B[0m" + (lines[i] ?? "");
|
|
2302
2626
|
this.out.write(s + SYNC_END);
|
|
2627
|
+
} else if (this.lifecycleMenu) {
|
|
2628
|
+
const lines = lifecycleMenuLines(
|
|
2629
|
+
this.lifecycleMenu.boxName,
|
|
2630
|
+
this.lifecycleMenu.state,
|
|
2631
|
+
this.lifecycleMenu.confirmDestroy,
|
|
2632
|
+
r.w,
|
|
2633
|
+
r.h
|
|
2634
|
+
);
|
|
2635
|
+
let s = SYNC_BEGIN + "\x1B[?25l";
|
|
2636
|
+
for (let i = 0; i < r.h; i++) s += cursorTo2(r.x, r.y + i) + "\x1B[0m" + (lines[i] ?? "");
|
|
2637
|
+
this.out.write(s + SYNC_END);
|
|
2303
2638
|
} else if (this.createMenu) {
|
|
2304
2639
|
const lines = createMenuLines(this.createMenu.where, r.w, r.h);
|
|
2305
2640
|
let s = SYNC_BEGIN + "\x1B[?25l";
|
|
@@ -2317,23 +2652,36 @@ var Compositor = class {
|
|
|
2317
2652
|
drawChrome() {
|
|
2318
2653
|
if (this.tornDown || this.layout.tooSmall) return;
|
|
2319
2654
|
const { sidebar, sepX, statusY } = this.layout;
|
|
2320
|
-
const lines = sidebarLines(
|
|
2321
|
-
|
|
2322
|
-
|
|
2655
|
+
const { lines, rowOwner, headerRows } = sidebarLines(
|
|
2656
|
+
this.boxes,
|
|
2657
|
+
this.selectedId,
|
|
2658
|
+
sidebar.w,
|
|
2659
|
+
sidebar.h
|
|
2660
|
+
);
|
|
2323
2661
|
let s = SYNC_BEGIN + "\x1B[0m";
|
|
2324
2662
|
for (let i = 0; i < lines.length; i++) {
|
|
2325
|
-
const style = i
|
|
2663
|
+
const style = headerRows[i] ? SB_HEADER : rowOwner[i] === this.selectedId ? SB_SELECTED : SB_BODY;
|
|
2326
2664
|
s += cursorTo2(0, i) + style + lines[i] + SGR_RESET;
|
|
2327
2665
|
}
|
|
2328
|
-
for (let y = 0; y < sidebar.h; y++)
|
|
2666
|
+
for (let y = 0; y < sidebar.h; y++)
|
|
2667
|
+
s += cursorTo2(sepX, y) + SB_HEADER + (y === 0 ? "\u256E" : "\u2502") + SGR_RESET;
|
|
2329
2668
|
let status;
|
|
2330
|
-
if (this.
|
|
2669
|
+
if (this.pendingConfirm) {
|
|
2670
|
+
const w = this.layout.cols;
|
|
2671
|
+
const txt = ` Destroy ${this.pendingConfirm.name}? y = confirm \xB7 any other key = cancel `.slice(0, w).padEnd(w);
|
|
2672
|
+
status = `\x1B[7m${txt}\x1B[0m`;
|
|
2673
|
+
} else if (this.flashMsg) {
|
|
2331
2674
|
const w = this.layout.cols;
|
|
2332
2675
|
const txt = ` ${this.flashMsg} `.slice(0, w).padEnd(w);
|
|
2333
2676
|
status = `\x1B[7m${txt}\x1B[0m`;
|
|
2334
2677
|
} else {
|
|
2335
2678
|
const stateLabel = this.selectedId === NEW_BOX_ID ? "create" : this.menu ? "menu" : this.session && this.activeMode === "shell" ? "shell" : void 0;
|
|
2336
|
-
status = statusLine(
|
|
2679
|
+
status = statusLine(
|
|
2680
|
+
this.selectedBox(),
|
|
2681
|
+
this.layout.cols,
|
|
2682
|
+
stateLabel,
|
|
2683
|
+
this.leaderActive ? ADVANCED_HINT_GROUPS : void 0
|
|
2684
|
+
);
|
|
2337
2685
|
}
|
|
2338
2686
|
s += cursorTo2(0, statusY) + status;
|
|
2339
2687
|
this.out.write(s + SYNC_END);
|
|
@@ -2360,6 +2708,7 @@ var Compositor = class {
|
|
|
2360
2708
|
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
2361
2709
|
if (this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
2362
2710
|
if (this.flashTimer) clearTimeout(this.flashTimer);
|
|
2711
|
+
if (this.leaderLingerTimer) clearTimeout(this.leaderLingerTimer);
|
|
2363
2712
|
this.parser.dispose();
|
|
2364
2713
|
this.disposeSession();
|
|
2365
2714
|
this.inp.off("data", this.onData);
|
|
@@ -2387,7 +2736,15 @@ function scoped(all, projectRoot, boxes) {
|
|
|
2387
2736
|
return sortBoxes(all ? boxes : boxes.filter((b) => b.projectRoot === projectRoot));
|
|
2388
2737
|
}
|
|
2389
2738
|
function toSidebar(b) {
|
|
2390
|
-
return {
|
|
2739
|
+
return {
|
|
2740
|
+
id: b.id,
|
|
2741
|
+
name: b.name,
|
|
2742
|
+
state: b.state,
|
|
2743
|
+
claudeActivity: b.claudeActivity,
|
|
2744
|
+
sessionTitle: b.claudeSessionTitle,
|
|
2745
|
+
index: b.projectIndex,
|
|
2746
|
+
project: b.projectRoot
|
|
2747
|
+
};
|
|
2391
2748
|
}
|
|
2392
2749
|
var dashboardCommand = new Command7("dashboard").description("Box list + the selected box live Agent session").argument("[box]", "initial box (default: first running box; -p restricts to the cwd project)").option("-p, --project", "only this project's boxes (default: all boxes globally)").action(async (idOrName, opts) => {
|
|
2393
2750
|
try {
|
|
@@ -2437,6 +2794,9 @@ var dashboardCommand = new Command7("dashboard").description("Box list + the sel
|
|
|
2437
2794
|
if (boxId === NEW_BOX_ID) return { kind: "create-menu", where: project.root };
|
|
2438
2795
|
const box = (await listBoxes()).find((b) => b.id === boxId);
|
|
2439
2796
|
if (!box) return { kind: "placeholder", lines: ["", " box not found"] };
|
|
2797
|
+
if (box.state === "paused" || box.state === "stopped") {
|
|
2798
|
+
return { kind: "lifecycle-menu", state: box.state };
|
|
2799
|
+
}
|
|
2440
2800
|
if (box.state !== "running") {
|
|
2441
2801
|
return {
|
|
2442
2802
|
kind: "placeholder",
|
|
@@ -2552,6 +2912,21 @@ var dashboardCommand = new Command7("dashboard").description("Box list + the sel
|
|
|
2552
2912
|
detach(process.execPath, [process.argv[1], "code", box.name, "--no-wait"]);
|
|
2553
2913
|
return "Launching VS Code / Cursor\u2026";
|
|
2554
2914
|
};
|
|
2915
|
+
const resumeBox = async (boxId) => {
|
|
2916
|
+
const box = (await listBoxes()).find((b) => b.id === boxId);
|
|
2917
|
+
if (!box) throw new Error("box not found");
|
|
2918
|
+
if (box.state === "paused") await unpauseBox(box.id);
|
|
2919
|
+
else await startBox(box.id);
|
|
2920
|
+
};
|
|
2921
|
+
const pauseBoxAction = async (boxId) => {
|
|
2922
|
+
await pauseBox(boxId);
|
|
2923
|
+
};
|
|
2924
|
+
const stopBoxAction = async (boxId) => {
|
|
2925
|
+
await stopBox(boxId);
|
|
2926
|
+
};
|
|
2927
|
+
const destroyBoxAction = async (boxId) => {
|
|
2928
|
+
await destroyBox(boxId);
|
|
2929
|
+
};
|
|
2555
2930
|
const compositor = new Compositor(
|
|
2556
2931
|
{
|
|
2557
2932
|
ptySpawn,
|
|
@@ -2561,6 +2936,10 @@ var dashboardCommand = new Command7("dashboard").description("Box list + the sel
|
|
|
2561
2936
|
startClaude,
|
|
2562
2937
|
openShell,
|
|
2563
2938
|
createNewBox,
|
|
2939
|
+
resumeBox,
|
|
2940
|
+
pauseBox: pauseBoxAction,
|
|
2941
|
+
stopBox: stopBoxAction,
|
|
2942
|
+
destroyBox: destroyBoxAction,
|
|
2564
2943
|
openVnc,
|
|
2565
2944
|
openCode,
|
|
2566
2945
|
openWeb
|
|
@@ -2745,28 +3124,40 @@ function renderTable(boxes, stream) {
|
|
|
2745
3124
|
(row2) => row2.map((cell, i) => padCell(cell ?? plain(""), i)).join(" ").trimEnd()
|
|
2746
3125
|
).join("\n");
|
|
2747
3126
|
}
|
|
2748
|
-
async function
|
|
3127
|
+
async function scopedBoxes(all) {
|
|
2749
3128
|
const boxes = await listBoxes();
|
|
2750
|
-
if (
|
|
3129
|
+
if (all) return { boxes, projectRoot: "", scoped: false };
|
|
3130
|
+
const { root } = await findProjectRoot(process.cwd());
|
|
3131
|
+
return { boxes: boxes.filter((b) => b.projectRoot === root), projectRoot: root, scoped: true };
|
|
3132
|
+
}
|
|
3133
|
+
async function buildListText(all) {
|
|
3134
|
+
const { boxes, projectRoot, scoped: scoped2 } = await scopedBoxes(all);
|
|
3135
|
+
if (boxes.length === 0) {
|
|
3136
|
+
if (scoped2) {
|
|
3137
|
+
return `no boxes in this project (${projectRoot}) \u2014 run \`agentbox create\`, or \`agentbox list --all\` to see all`;
|
|
3138
|
+
}
|
|
3139
|
+
return "no boxes \u2014 run `agentbox create` to make one";
|
|
3140
|
+
}
|
|
2751
3141
|
return renderTable(boxes, process.stdout);
|
|
2752
3142
|
}
|
|
2753
3143
|
var listCommand2 = withWatchOptions(
|
|
2754
|
-
new Command9("list").alias("ls").description("List
|
|
3144
|
+
new Command9("list").alias("ls").description("List agent boxes in the current project (-a for all)").option("-j, --json", "machine-readable JSON output").option("-a, --all", "include boxes from all projects")
|
|
2755
3145
|
).action(async (opts) => {
|
|
2756
3146
|
if (opts.json && opts.watch) {
|
|
2757
3147
|
log11.error("cannot combine --json with --watch");
|
|
2758
3148
|
process.exit(2);
|
|
2759
3149
|
}
|
|
3150
|
+
const all = opts.all ?? false;
|
|
2760
3151
|
if (opts.watch) {
|
|
2761
|
-
await watchRender(buildListText, opts.interval);
|
|
3152
|
+
await watchRender(() => buildListText(all), opts.interval);
|
|
2762
3153
|
return;
|
|
2763
3154
|
}
|
|
2764
3155
|
if (opts.json) {
|
|
2765
|
-
const boxes = await
|
|
3156
|
+
const { boxes } = await scopedBoxes(all);
|
|
2766
3157
|
process.stdout.write(JSON.stringify(boxes, null, 2) + "\n");
|
|
2767
3158
|
return;
|
|
2768
3159
|
}
|
|
2769
|
-
process.stdout.write(await buildListText() + "\n");
|
|
3160
|
+
process.stdout.write(await buildListText(all) + "\n");
|
|
2770
3161
|
});
|
|
2771
3162
|
|
|
2772
3163
|
// src/commands/logs.ts
|