@madarco/agentbox 0.2.0 → 0.4.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-3NU4PS5W.js → chunk-3JKQNOXP.js} +11 -4
- package/dist/chunk-3JKQNOXP.js.map +1 -0
- package/dist/{chunk-3OPLNXQ5.js → chunk-3NCUES35.js} +2 -2
- package/dist/{chunk-3NIZKZPJ.js → chunk-MOC54XL6.js} +1 -1
- package/dist/{chunk-3NIZKZPJ.js.map → chunk-MOC54XL6.js.map} +1 -1
- package/dist/{create-SE6H4B5U-ESNDTXAI.js → create-SE6H4B5U-IWAZHJHV.js} +3 -3
- package/dist/index.js +342 -62
- package/dist/index.js.map +1 -1
- package/dist/{lifecycle-A4QHFADW-4DREGGAE.js → lifecycle-YTMZYKOE-TD5S5FTS.js} +3 -3
- package/package.json +3 -3
- package/runtime/docker/Dockerfile.box +1 -0
- package/runtime/docker/packages/ctl/dist/bin.cjs +24 -3
- package/dist/chunk-3NU4PS5W.js.map +0 -1
- /package/dist/{chunk-3OPLNXQ5.js.map → chunk-3NCUES35.js.map} +0 -0
- /package/dist/{create-SE6H4B5U-ESNDTXAI.js.map → create-SE6H4B5U-IWAZHJHV.js.map} +0 -0
- /package/dist/{lifecycle-A4QHFADW-4DREGGAE.js.map → lifecycle-YTMZYKOE-TD5S5FTS.js.map} +0 -0
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
createBox,
|
|
4
4
|
defaultBoxName,
|
|
5
5
|
sanitizeBasename
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-3NCUES35.js";
|
|
7
|
+
import "./chunk-MOC54XL6.js";
|
|
8
8
|
import "./chunk-IDR4HVIC.js";
|
|
9
9
|
import "./chunk-SOMIKEN2.js";
|
|
10
10
|
export {
|
|
@@ -12,4 +12,4 @@ export {
|
|
|
12
12
|
defaultBoxName,
|
|
13
13
|
sanitizeBasename
|
|
14
14
|
};
|
|
15
|
-
//# sourceMappingURL=create-SE6H4B5U-
|
|
15
|
+
//# sourceMappingURL=create-SE6H4B5U-IWAZHJHV.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
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,
|
|
8
8
|
destroyBox,
|
|
9
|
+
getBoxEndpoints,
|
|
9
10
|
getBoxHostPaths,
|
|
10
11
|
inspectBox,
|
|
11
12
|
listBoxes,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
startBox,
|
|
16
17
|
stopBox,
|
|
17
18
|
unpauseBox
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-3JKQNOXP.js";
|
|
19
20
|
import {
|
|
20
21
|
ClaudeSessionError,
|
|
21
22
|
SHARED_CLAUDE_VOLUME,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
resolveClaudeVolume,
|
|
38
39
|
startClaudeSession,
|
|
39
40
|
stopRelay
|
|
40
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-MOC54XL6.js";
|
|
41
42
|
import {
|
|
42
43
|
STATE_DIR,
|
|
43
44
|
readState,
|
|
@@ -66,6 +67,7 @@ import {
|
|
|
66
67
|
lookupKey,
|
|
67
68
|
pruneOrphanProjectConfigs,
|
|
68
69
|
pullToHost,
|
|
70
|
+
readBoxStatus,
|
|
69
71
|
refreshExport,
|
|
70
72
|
removeCheckpoint,
|
|
71
73
|
removeImage,
|
|
@@ -1600,6 +1602,7 @@ var InputParser = class {
|
|
|
1600
1602
|
if (b === LEADER) {
|
|
1601
1603
|
this.flush();
|
|
1602
1604
|
this.state = "leader";
|
|
1605
|
+
this.onEvent({ type: "leader", active: true });
|
|
1603
1606
|
this.arm(this.leaderMs, "leader");
|
|
1604
1607
|
} else if (b === ESC) {
|
|
1605
1608
|
this.flush();
|
|
@@ -1622,14 +1625,18 @@ var InputParser = class {
|
|
|
1622
1625
|
if (c === "v") this.onEvent({ type: "action", name: "vnc" });
|
|
1623
1626
|
else if (c === "w") this.onEvent({ type: "action", name: "web" });
|
|
1624
1627
|
else if (c === "c") this.onEvent({ type: "action", name: "code" });
|
|
1625
|
-
else if (c === "
|
|
1626
|
-
else if (c === "
|
|
1628
|
+
else if (c === "s") this.onEvent({ type: "action", name: "stop" });
|
|
1629
|
+
else if (c === "p") this.onEvent({ type: "action", name: "pause" });
|
|
1630
|
+
else if (c === "d") this.onEvent({ type: "action", name: "destroy" });
|
|
1631
|
+
else if (c === "q") this.onEvent({ type: "quit" });
|
|
1632
|
+
else if (c === "k") this.onEvent({ type: "switch", dir: "prev" });
|
|
1627
1633
|
else if (c === "j" || c === "n" || c === "N") this.onEvent({ type: "switch", dir: "next" });
|
|
1628
1634
|
else {
|
|
1629
1635
|
this.fwd.push(b);
|
|
1630
1636
|
this.flush();
|
|
1631
1637
|
}
|
|
1632
1638
|
}
|
|
1639
|
+
this.onEvent({ type: "leader", active: false });
|
|
1633
1640
|
this.state = "normal";
|
|
1634
1641
|
i++;
|
|
1635
1642
|
continue;
|
|
@@ -1737,6 +1744,7 @@ var InputParser = class {
|
|
|
1737
1744
|
if (kind === "leader" && this.state === "leader") {
|
|
1738
1745
|
this.fwd.push(LEADER);
|
|
1739
1746
|
this.flush();
|
|
1747
|
+
this.onEvent({ type: "leader", active: false });
|
|
1740
1748
|
this.state = "normal";
|
|
1741
1749
|
} else if (kind === "esc" && (this.state === "esc" || this.state === "mouseX10")) {
|
|
1742
1750
|
this.forwardVerbatim(this.esc);
|
|
@@ -1861,6 +1869,18 @@ var PtySession = class {
|
|
|
1861
1869
|
};
|
|
1862
1870
|
|
|
1863
1871
|
// src/dashboard/sidebar.ts
|
|
1872
|
+
function ellipsize(s, max) {
|
|
1873
|
+
if (max <= 0) return "";
|
|
1874
|
+
if (s.length <= max) return s;
|
|
1875
|
+
if (max === 1) return "\u2026";
|
|
1876
|
+
return s.slice(0, max - 1) + "\u2026";
|
|
1877
|
+
}
|
|
1878
|
+
function ellipsizeHead(s, max) {
|
|
1879
|
+
if (max <= 0) return "";
|
|
1880
|
+
if (s.length <= max) return s;
|
|
1881
|
+
if (max === 1) return "\u2026";
|
|
1882
|
+
return "\u2026" + s.slice(s.length - (max - 1));
|
|
1883
|
+
}
|
|
1864
1884
|
function activityCell(b) {
|
|
1865
1885
|
if (b.state !== "running") return `[${b.state}]`;
|
|
1866
1886
|
switch (b.claudeActivity) {
|
|
@@ -1876,8 +1896,12 @@ function activityCell(b) {
|
|
|
1876
1896
|
}
|
|
1877
1897
|
var NEW_BOX_ID = "__agentbox_new__";
|
|
1878
1898
|
var NEW_BOX_LABEL = "+ New box";
|
|
1879
|
-
var SIDEBAR_HEADER = "
|
|
1880
|
-
|
|
1899
|
+
var SIDEBAR_HEADER = "AgentBox";
|
|
1900
|
+
function topBorder(label, w) {
|
|
1901
|
+
const lead = `\u256D\u2500\u2500\u2500 ${label} `;
|
|
1902
|
+
if (lead.length >= w) return lead.slice(0, w);
|
|
1903
|
+
return lead + "\u2500".repeat(w - lead.length);
|
|
1904
|
+
}
|
|
1881
1905
|
function fit(s, w) {
|
|
1882
1906
|
if (s.length === w) return s;
|
|
1883
1907
|
if (s.length > w) return s.slice(0, w);
|
|
@@ -1889,17 +1913,55 @@ function center(s, w) {
|
|
|
1889
1913
|
const leftPad = Math.floor(pad / 2);
|
|
1890
1914
|
return " ".repeat(leftPad) + s + " ".repeat(pad - leftPad);
|
|
1891
1915
|
}
|
|
1916
|
+
function projectLabel(project) {
|
|
1917
|
+
if (!project) return "(no project)";
|
|
1918
|
+
const parts = project.split("/").filter(Boolean);
|
|
1919
|
+
return parts[parts.length - 1] ?? project;
|
|
1920
|
+
}
|
|
1921
|
+
function stripTitleGlyph(s) {
|
|
1922
|
+
const t = s.replace(/^[\s\p{S}*·]+/u, "");
|
|
1923
|
+
return t.length > 0 ? t : s.trim();
|
|
1924
|
+
}
|
|
1925
|
+
function boxRow(b, marker, w) {
|
|
1926
|
+
const numStr = b.index != null ? `${b.index} ` : "";
|
|
1927
|
+
const status = activityCell(b);
|
|
1928
|
+
const left = `${marker}${numStr}`;
|
|
1929
|
+
const room = w - left.length - status.length - 1;
|
|
1930
|
+
if (room <= 0) return fit(`${left}${status}`, w);
|
|
1931
|
+
const middle = b.state === "running" && b.sessionTitle ? ellipsize(stripTitleGlyph(b.sessionTitle), room) : ellipsizeHead(b.name, room);
|
|
1932
|
+
return fit(`${left}${middle}`, w - status.length) + status;
|
|
1933
|
+
}
|
|
1892
1934
|
function sidebarLines(boxes, selectedId, w, h) {
|
|
1893
|
-
const lines = [
|
|
1894
|
-
const
|
|
1935
|
+
const lines = [topBorder(SIDEBAR_HEADER, w), fit("", w)];
|
|
1936
|
+
const rowOwner = [null, null];
|
|
1937
|
+
const headerRows = [true, false];
|
|
1938
|
+
const push = (line, owner, header) => {
|
|
1939
|
+
lines.push(fit(line, w));
|
|
1940
|
+
rowOwner.push(owner);
|
|
1941
|
+
headerRows.push(header);
|
|
1942
|
+
};
|
|
1943
|
+
let prevProject;
|
|
1944
|
+
let seenGroup = false;
|
|
1895
1945
|
for (const b of boxes) {
|
|
1896
|
-
const marker = b.id === selectedId ? "\u25B8
|
|
1897
|
-
|
|
1898
|
-
|
|
1946
|
+
const marker = b.id === selectedId ? "\u25B8" : " ";
|
|
1947
|
+
if (b.id === NEW_BOX_ID) {
|
|
1948
|
+
push(`${marker}${NEW_BOX_LABEL}`, b.id, false);
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
if (!seenGroup || b.project !== prevProject) {
|
|
1952
|
+
push(center(` \u2500\u2500 ${projectLabel(b.project)} \u2500\u2500 `, w), null, true);
|
|
1953
|
+
prevProject = b.project;
|
|
1954
|
+
seenGroup = true;
|
|
1955
|
+
}
|
|
1956
|
+
push(boxRow(b, marker, w), b.id, false);
|
|
1899
1957
|
}
|
|
1900
|
-
if (boxes.length === 0)
|
|
1901
|
-
while (lines.length < h)
|
|
1902
|
-
return
|
|
1958
|
+
if (boxes.length === 0) push(" (no boxes)", null, false);
|
|
1959
|
+
while (lines.length < h) push("", null, false);
|
|
1960
|
+
return {
|
|
1961
|
+
lines: lines.slice(0, h),
|
|
1962
|
+
rowOwner: rowOwner.slice(0, h),
|
|
1963
|
+
headerRows: headerRows.slice(0, h)
|
|
1964
|
+
};
|
|
1903
1965
|
}
|
|
1904
1966
|
function menuLines(boxName, w, h) {
|
|
1905
1967
|
const body = [
|
|
@@ -1963,29 +2025,58 @@ var BRAND_NOBOLD = "\x1B[22m";
|
|
|
1963
2025
|
var HINT_KEY = "\x1B[38;5;255m";
|
|
1964
2026
|
var HINT_TXT = "\x1B[38;5;245m";
|
|
1965
2027
|
var BAR_RESET = "\x1B[0m";
|
|
2028
|
+
var SWITCH_HINT = ["Control+Option+\u2191/\u2193", "switch"];
|
|
1966
2029
|
var HINT_GROUPS = [
|
|
1967
|
-
|
|
2030
|
+
SWITCH_HINT,
|
|
1968
2031
|
["Control+a c", "code"],
|
|
1969
2032
|
["Control+a v", "vnc"],
|
|
1970
2033
|
["Control+a w", "web"],
|
|
1971
2034
|
["Control+a q", "quit"]
|
|
1972
2035
|
];
|
|
1973
|
-
|
|
2036
|
+
var COLLAPSED_HINT_GROUPS = [
|
|
2037
|
+
SWITCH_HINT,
|
|
2038
|
+
["Control+a", "more"]
|
|
2039
|
+
];
|
|
2040
|
+
var ADVANCED_HINT_GROUPS = [
|
|
2041
|
+
["c", "code"],
|
|
2042
|
+
["v", "vnc"],
|
|
2043
|
+
["w", "web"],
|
|
2044
|
+
["s", "stop"],
|
|
2045
|
+
["p", "pause"],
|
|
2046
|
+
["d", "destroy"],
|
|
2047
|
+
["q", "quit"]
|
|
2048
|
+
];
|
|
2049
|
+
function statusLine(box, w, stateLabel, groups = HINT_GROUPS) {
|
|
1974
2050
|
const state = stateLabel ?? (box ? box.state === "running" ? box.claudeActivity ?? "unknown" : box.state : "");
|
|
1975
2051
|
const brandPrefix = box ? " agentbox \u25B8 " : " agentbox ";
|
|
1976
|
-
const
|
|
1977
|
-
const
|
|
1978
|
-
const
|
|
2052
|
+
const base = box ? `${box.name} (${state})` : "";
|
|
2053
|
+
const coreMain = box ? `${base} ` : "";
|
|
2054
|
+
const corePlain = brandPrefix + coreMain;
|
|
1979
2055
|
const SEP = " \u2502 ";
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
`${HINT_TXT}${SEP}`
|
|
1983
|
-
)
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2056
|
+
const renderHints = (g) => ({
|
|
2057
|
+
plain: g.map(([k, l]) => `${k}: ${l}`).join(SEP) + " ",
|
|
2058
|
+
styled: g.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(`${HINT_TXT}${SEP}`) + " "
|
|
2059
|
+
});
|
|
2060
|
+
let hints = null;
|
|
2061
|
+
for (const g of [groups, COLLAPSED_HINT_GROUPS]) {
|
|
2062
|
+
const h = renderHints(g);
|
|
2063
|
+
if (corePlain.length + h.plain.length + 1 <= w) {
|
|
2064
|
+
hints = h;
|
|
2065
|
+
break;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
if (!hints) {
|
|
2069
|
+
return BAR_BASE + BAR_BRAND + fit(corePlain, w) + BAR_RESET;
|
|
2070
|
+
}
|
|
2071
|
+
const room = w - corePlain.length - hints.plain.length - 1;
|
|
2072
|
+
let titleSeg = "";
|
|
2073
|
+
if (box?.sessionTitle && room >= 7) {
|
|
2074
|
+
titleSeg = ` \u2014 ${ellipsize(box.sessionTitle, Math.min(40, room - 3))}`;
|
|
2075
|
+
}
|
|
2076
|
+
const leftPlain = brandPrefix + base + titleSeg + (box ? " " : "");
|
|
2077
|
+
const leftStyled = BAR_BRAND + brandPrefix + BRAND_BOLD + base + titleSeg + (box ? " " : "") + BRAND_NOBOLD;
|
|
2078
|
+
const gap = w - leftPlain.length - hints.plain.length;
|
|
2079
|
+
return BAR_BASE + leftStyled + BAR_BASE + " ".repeat(gap) + hints.styled + BAR_RESET;
|
|
1989
2080
|
}
|
|
1990
2081
|
|
|
1991
2082
|
// src/dashboard/compositor.ts
|
|
@@ -1996,6 +2087,7 @@ var SGR_RESET = "\x1B[0m";
|
|
|
1996
2087
|
var POLL_MS = 1e3;
|
|
1997
2088
|
var FRAME_MS = 16;
|
|
1998
2089
|
var RESIZE_DEBOUNCE_MS = 120;
|
|
2090
|
+
var LEADER_LINGER_MS = 1500;
|
|
1999
2091
|
var SYNC_BEGIN = "\x1B[?2026h";
|
|
2000
2092
|
var SYNC_END = "\x1B[?2026l";
|
|
2001
2093
|
function cursorTo2(x, y) {
|
|
@@ -2008,10 +2100,40 @@ var Compositor = class {
|
|
|
2008
2100
|
this.layout = computeLayout(this.out.columns ?? 100, this.out.rows ?? 30);
|
|
2009
2101
|
this.parser = new InputParser({
|
|
2010
2102
|
onEvent: (e) => {
|
|
2103
|
+
if (e.type === "leader") {
|
|
2104
|
+
if (this.leaderLingerTimer) {
|
|
2105
|
+
clearTimeout(this.leaderLingerTimer);
|
|
2106
|
+
this.leaderLingerTimer = null;
|
|
2107
|
+
}
|
|
2108
|
+
if (e.active) {
|
|
2109
|
+
this.leaderActive = true;
|
|
2110
|
+
} else {
|
|
2111
|
+
this.leaderLingerTimer = setTimeout(() => {
|
|
2112
|
+
this.leaderLingerTimer = null;
|
|
2113
|
+
this.leaderActive = false;
|
|
2114
|
+
this.drawChrome();
|
|
2115
|
+
}, LEADER_LINGER_MS);
|
|
2116
|
+
}
|
|
2117
|
+
this.drawChrome();
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
if (this.pendingConfirm) {
|
|
2121
|
+
if (e.type === "forward") {
|
|
2122
|
+
this.handleConfirmKey(e.bytes);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
this.pendingConfirm = null;
|
|
2126
|
+
this.drawChrome();
|
|
2127
|
+
}
|
|
2011
2128
|
if (e.type === "quit") this.onSig();
|
|
2012
2129
|
else if (e.type === "switch") this.switchBox(e.dir);
|
|
2013
|
-
else if (e.type === "action")
|
|
2014
|
-
|
|
2130
|
+
else if (e.type === "action") {
|
|
2131
|
+
if (e.name === "pause" || e.name === "stop" || e.name === "destroy") {
|
|
2132
|
+
void this.doLifecycle(e.name);
|
|
2133
|
+
} else {
|
|
2134
|
+
void this.doAction(e.name);
|
|
2135
|
+
}
|
|
2136
|
+
} else if (this.createMenu) this.handleCreateMenuKey(e.bytes);
|
|
2015
2137
|
else if (this.lifecycleMenu) this.handleLifecycleMenuKey(e.bytes);
|
|
2016
2138
|
else if (this.menu) this.handleMenuKey(e.bytes);
|
|
2017
2139
|
else this.session?.write(e.bytes);
|
|
@@ -2038,6 +2160,14 @@ var Compositor = class {
|
|
|
2038
2160
|
menu = null;
|
|
2039
2161
|
lifecycleMenu = null;
|
|
2040
2162
|
createMenu = null;
|
|
2163
|
+
/** True while the Ctrl-a leader is pending — swaps the footer to the
|
|
2164
|
+
* expanded chord menu (chrome only; never touches the right pane). */
|
|
2165
|
+
leaderActive = false;
|
|
2166
|
+
/** Holds the expanded footer for LEADER_LINGER_MS after the leader resolves
|
|
2167
|
+
* (so the chord menu doesn't flash by). */
|
|
2168
|
+
leaderLingerTimer = null;
|
|
2169
|
+
/** Set while a destroy confirm is pending in the status bar. */
|
|
2170
|
+
pendingConfirm = null;
|
|
2041
2171
|
activeMode = "claude";
|
|
2042
2172
|
flashMsg = null;
|
|
2043
2173
|
flashTimer = null;
|
|
@@ -2097,7 +2227,9 @@ var Compositor = class {
|
|
|
2097
2227
|
return this.boxes.find((b) => b.id === this.selectedId);
|
|
2098
2228
|
}
|
|
2099
2229
|
async poll() {
|
|
2100
|
-
const before = JSON.stringify(
|
|
2230
|
+
const before = JSON.stringify(
|
|
2231
|
+
this.boxes.map((b) => [b.id, b.state, b.claudeActivity, b.sessionTitle])
|
|
2232
|
+
);
|
|
2101
2233
|
await this.refreshBoxes();
|
|
2102
2234
|
if (this.busy) {
|
|
2103
2235
|
} else if (!this.boxes.some((b) => b.id === this.selectedId) && this.boxes[0]) {
|
|
@@ -2109,7 +2241,9 @@ var Compositor = class {
|
|
|
2109
2241
|
const reresolve = this.session && !running || this.placeholder && running || this.menu && !running || this.lifecycleMenu != null && box?.state !== this.lifecycleMenu.state;
|
|
2110
2242
|
if (reresolve) await this.spawnActive();
|
|
2111
2243
|
}
|
|
2112
|
-
if (JSON.stringify(
|
|
2244
|
+
if (JSON.stringify(
|
|
2245
|
+
this.boxes.map((b) => [b.id, b.state, b.claudeActivity, b.sessionTitle])
|
|
2246
|
+
) !== before) {
|
|
2113
2247
|
this.drawChrome();
|
|
2114
2248
|
}
|
|
2115
2249
|
}
|
|
@@ -2124,6 +2258,7 @@ var Compositor = class {
|
|
|
2124
2258
|
this.menu = null;
|
|
2125
2259
|
this.lifecycleMenu = null;
|
|
2126
2260
|
this.createMenu = null;
|
|
2261
|
+
this.pendingConfirm = null;
|
|
2127
2262
|
this.clearRightPane();
|
|
2128
2263
|
const id = this.selectedId;
|
|
2129
2264
|
const target = await this.deps.resolveTarget(id);
|
|
@@ -2137,6 +2272,7 @@ var Compositor = class {
|
|
|
2137
2272
|
this.menu = null;
|
|
2138
2273
|
this.lifecycleMenu = null;
|
|
2139
2274
|
this.createMenu = null;
|
|
2275
|
+
this.pendingConfirm = null;
|
|
2140
2276
|
if (target.kind === "attach") {
|
|
2141
2277
|
this.activeMode = target.mode ?? "claude";
|
|
2142
2278
|
this.session = new PtySession(
|
|
@@ -2214,7 +2350,7 @@ var Compositor = class {
|
|
|
2214
2350
|
for (const b of bytes) {
|
|
2215
2351
|
if (m.confirmDestroy) {
|
|
2216
2352
|
if (b === 121 || b === 13 || b === 10) {
|
|
2217
|
-
void this.
|
|
2353
|
+
void this.runDestroy(this.selectedId, this.selectedBox()?.name ?? this.selectedId);
|
|
2218
2354
|
} else {
|
|
2219
2355
|
m.confirmDestroy = false;
|
|
2220
2356
|
this.drawChrome();
|
|
@@ -2224,7 +2360,7 @@ var Compositor = class {
|
|
|
2224
2360
|
}
|
|
2225
2361
|
const resumeKey = m.state === "paused" ? 117 : 115;
|
|
2226
2362
|
if (b === resumeKey) {
|
|
2227
|
-
void this.
|
|
2363
|
+
void this.resumeSelected();
|
|
2228
2364
|
return;
|
|
2229
2365
|
}
|
|
2230
2366
|
if (b === 100) {
|
|
@@ -2235,37 +2371,27 @@ var Compositor = class {
|
|
|
2235
2371
|
}
|
|
2236
2372
|
}
|
|
2237
2373
|
}
|
|
2238
|
-
async
|
|
2374
|
+
async resumeSelected() {
|
|
2239
2375
|
if (this.busy) return;
|
|
2240
2376
|
const id = this.selectedId;
|
|
2241
2377
|
const name = this.selectedBox()?.name ?? id;
|
|
2242
|
-
const
|
|
2378
|
+
const verb = this.lifecycleMenu?.state === "stopped" ? "start" : "unpause";
|
|
2243
2379
|
this.busy = true;
|
|
2244
2380
|
this.menu = null;
|
|
2245
2381
|
this.lifecycleMenu = null;
|
|
2246
2382
|
this.createMenu = null;
|
|
2247
|
-
this.placeholder = ["",
|
|
2383
|
+
this.placeholder = ["", " Resuming\u2026"];
|
|
2248
2384
|
this.prevRows = null;
|
|
2249
2385
|
this.drawChrome();
|
|
2250
2386
|
this.scheduleRender();
|
|
2251
2387
|
try {
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
await this.spawnActive();
|
|
2257
|
-
} else {
|
|
2258
|
-
await this.deps.destroyBox(id);
|
|
2259
|
-
if (this.tornDown) return;
|
|
2260
|
-
await this.refreshBoxes();
|
|
2261
|
-
if (this.boxes[0]) this.selectedId = this.boxes[0].id;
|
|
2262
|
-
await this.spawnActive();
|
|
2263
|
-
this.flash(`destroyed ${name}`);
|
|
2264
|
-
}
|
|
2388
|
+
await this.deps.resumeBox(id);
|
|
2389
|
+
if (this.selectedId !== id || this.tornDown) return;
|
|
2390
|
+
await this.refreshBoxes();
|
|
2391
|
+
await this.spawnActive();
|
|
2265
2392
|
} catch (err) {
|
|
2266
2393
|
if (this.selectedId !== id || this.tornDown) return;
|
|
2267
2394
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2268
|
-
const verb = which === "resume" ? resumeVerb : "destroy";
|
|
2269
2395
|
this.placeholder = [
|
|
2270
2396
|
"",
|
|
2271
2397
|
` Failed to ${verb} ${name}:`,
|
|
@@ -2279,6 +2405,102 @@ var Compositor = class {
|
|
|
2279
2405
|
this.busy = false;
|
|
2280
2406
|
}
|
|
2281
2407
|
}
|
|
2408
|
+
/** Destroy `id` and recover the selection. Shared by the lifecycle-menu
|
|
2409
|
+
* confirm and the running-box `Ctrl-a d` status-bar confirm. */
|
|
2410
|
+
async runDestroy(id, name) {
|
|
2411
|
+
if (this.busy) return;
|
|
2412
|
+
this.busy = true;
|
|
2413
|
+
this.menu = null;
|
|
2414
|
+
this.lifecycleMenu = null;
|
|
2415
|
+
this.createMenu = null;
|
|
2416
|
+
this.placeholder = ["", " Destroying\u2026"];
|
|
2417
|
+
this.prevRows = null;
|
|
2418
|
+
this.drawChrome();
|
|
2419
|
+
this.scheduleRender();
|
|
2420
|
+
try {
|
|
2421
|
+
await this.deps.destroyBox(id);
|
|
2422
|
+
if (this.tornDown) return;
|
|
2423
|
+
await this.refreshBoxes();
|
|
2424
|
+
if (this.boxes[0]) this.selectedId = this.boxes[0].id;
|
|
2425
|
+
await this.spawnActive();
|
|
2426
|
+
this.flash(`destroyed ${name}`);
|
|
2427
|
+
} catch (err) {
|
|
2428
|
+
if (this.tornDown) return;
|
|
2429
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2430
|
+
this.placeholder = [
|
|
2431
|
+
"",
|
|
2432
|
+
` Failed to destroy ${name}:`,
|
|
2433
|
+
` ${msg}`,
|
|
2434
|
+
"",
|
|
2435
|
+
` Try from a shell: agentbox destroy ${name}`
|
|
2436
|
+
];
|
|
2437
|
+
this.prevRows = null;
|
|
2438
|
+
this.scheduleRender();
|
|
2439
|
+
} finally {
|
|
2440
|
+
this.busy = false;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
/** Ctrl-a p/s/d on the selected box. pause/stop transition state (the pane
|
|
2444
|
+
* re-resolves to the lifecycle menu); destroy asks to confirm first. */
|
|
2445
|
+
async doLifecycle(name) {
|
|
2446
|
+
if (this.selectedId === NEW_BOX_ID) {
|
|
2447
|
+
this.flash("select a box first");
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
const id = this.selectedId;
|
|
2451
|
+
const boxName = this.selectedBox()?.name ?? id;
|
|
2452
|
+
if (name === "destroy") {
|
|
2453
|
+
this.pendingConfirm = { boxId: id, name: boxName };
|
|
2454
|
+
this.drawChrome();
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2457
|
+
if (this.selectedBox()?.state !== "running") {
|
|
2458
|
+
this.flash(`${boxName} is not running`);
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
if (this.busy) return;
|
|
2462
|
+
this.busy = true;
|
|
2463
|
+
this.menu = null;
|
|
2464
|
+
this.lifecycleMenu = null;
|
|
2465
|
+
this.createMenu = null;
|
|
2466
|
+
this.placeholder = ["", name === "pause" ? " Pausing\u2026" : " Stopping\u2026"];
|
|
2467
|
+
this.prevRows = null;
|
|
2468
|
+
this.drawChrome();
|
|
2469
|
+
this.scheduleRender();
|
|
2470
|
+
try {
|
|
2471
|
+
if (name === "pause") await this.deps.pauseBox(id);
|
|
2472
|
+
else await this.deps.stopBox(id);
|
|
2473
|
+
if (this.selectedId !== id || this.tornDown) return;
|
|
2474
|
+
await this.refreshBoxes();
|
|
2475
|
+
await this.spawnActive();
|
|
2476
|
+
this.flash(`${name === "pause" ? "paused" : "stopped"} ${boxName}`);
|
|
2477
|
+
} catch (err) {
|
|
2478
|
+
if (this.selectedId !== id || this.tornDown) return;
|
|
2479
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2480
|
+
this.placeholder = [
|
|
2481
|
+
"",
|
|
2482
|
+
` Failed to ${name} ${boxName}:`,
|
|
2483
|
+
` ${msg}`,
|
|
2484
|
+
"",
|
|
2485
|
+
` Try from a shell: agentbox ${name} ${boxName}`
|
|
2486
|
+
];
|
|
2487
|
+
this.prevRows = null;
|
|
2488
|
+
this.scheduleRender();
|
|
2489
|
+
} finally {
|
|
2490
|
+
this.busy = false;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
handleConfirmKey(bytes) {
|
|
2494
|
+
const c = this.pendingConfirm;
|
|
2495
|
+
if (!c) return;
|
|
2496
|
+
const b = bytes[0];
|
|
2497
|
+
this.pendingConfirm = null;
|
|
2498
|
+
if (b === 121 || b === 13 || b === 10) {
|
|
2499
|
+
void this.runDestroy(c.boxId, c.name);
|
|
2500
|
+
} else {
|
|
2501
|
+
this.drawChrome();
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2282
2504
|
handleCreateMenuKey(bytes) {
|
|
2283
2505
|
for (const b of bytes) {
|
|
2284
2506
|
if (b === 99 || b === 13 || b === 10) {
|
|
@@ -2360,6 +2582,7 @@ var Compositor = class {
|
|
|
2360
2582
|
}
|
|
2361
2583
|
switchBox(dir) {
|
|
2362
2584
|
if (this.boxes.length === 0) return;
|
|
2585
|
+
this.pendingConfirm = null;
|
|
2363
2586
|
const i = Math.max(
|
|
2364
2587
|
0,
|
|
2365
2588
|
this.boxes.findIndex((b) => b.id === this.selectedId)
|
|
@@ -2431,23 +2654,36 @@ var Compositor = class {
|
|
|
2431
2654
|
drawChrome() {
|
|
2432
2655
|
if (this.tornDown || this.layout.tooSmall) return;
|
|
2433
2656
|
const { sidebar, sepX, statusY } = this.layout;
|
|
2434
|
-
const lines = sidebarLines(
|
|
2435
|
-
|
|
2436
|
-
|
|
2657
|
+
const { lines, rowOwner, headerRows } = sidebarLines(
|
|
2658
|
+
this.boxes,
|
|
2659
|
+
this.selectedId,
|
|
2660
|
+
sidebar.w,
|
|
2661
|
+
sidebar.h
|
|
2662
|
+
);
|
|
2437
2663
|
let s = SYNC_BEGIN + "\x1B[0m";
|
|
2438
2664
|
for (let i = 0; i < lines.length; i++) {
|
|
2439
|
-
const style = i
|
|
2665
|
+
const style = headerRows[i] ? SB_HEADER : rowOwner[i] === this.selectedId ? SB_SELECTED : SB_BODY;
|
|
2440
2666
|
s += cursorTo2(0, i) + style + lines[i] + SGR_RESET;
|
|
2441
2667
|
}
|
|
2442
|
-
for (let y = 0; y < sidebar.h; y++)
|
|
2668
|
+
for (let y = 0; y < sidebar.h; y++)
|
|
2669
|
+
s += cursorTo2(sepX, y) + SB_HEADER + (y === 0 ? "\u256E" : "\u2502") + SGR_RESET;
|
|
2443
2670
|
let status;
|
|
2444
|
-
if (this.
|
|
2671
|
+
if (this.pendingConfirm) {
|
|
2672
|
+
const w = this.layout.cols;
|
|
2673
|
+
const txt = ` Destroy ${this.pendingConfirm.name}? y = confirm \xB7 any other key = cancel `.slice(0, w).padEnd(w);
|
|
2674
|
+
status = `\x1B[7m${txt}\x1B[0m`;
|
|
2675
|
+
} else if (this.flashMsg) {
|
|
2445
2676
|
const w = this.layout.cols;
|
|
2446
2677
|
const txt = ` ${this.flashMsg} `.slice(0, w).padEnd(w);
|
|
2447
2678
|
status = `\x1B[7m${txt}\x1B[0m`;
|
|
2448
2679
|
} else {
|
|
2449
2680
|
const stateLabel = this.selectedId === NEW_BOX_ID ? "create" : this.menu ? "menu" : this.session && this.activeMode === "shell" ? "shell" : void 0;
|
|
2450
|
-
status = statusLine(
|
|
2681
|
+
status = statusLine(
|
|
2682
|
+
this.selectedBox(),
|
|
2683
|
+
this.layout.cols,
|
|
2684
|
+
stateLabel,
|
|
2685
|
+
this.leaderActive ? ADVANCED_HINT_GROUPS : void 0
|
|
2686
|
+
);
|
|
2451
2687
|
}
|
|
2452
2688
|
s += cursorTo2(0, statusY) + status;
|
|
2453
2689
|
this.out.write(s + SYNC_END);
|
|
@@ -2474,6 +2710,7 @@ var Compositor = class {
|
|
|
2474
2710
|
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
2475
2711
|
if (this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
2476
2712
|
if (this.flashTimer) clearTimeout(this.flashTimer);
|
|
2713
|
+
if (this.leaderLingerTimer) clearTimeout(this.leaderLingerTimer);
|
|
2477
2714
|
this.parser.dispose();
|
|
2478
2715
|
this.disposeSession();
|
|
2479
2716
|
this.inp.off("data", this.onData);
|
|
@@ -2501,7 +2738,15 @@ function scoped(all, projectRoot, boxes) {
|
|
|
2501
2738
|
return sortBoxes(all ? boxes : boxes.filter((b) => b.projectRoot === projectRoot));
|
|
2502
2739
|
}
|
|
2503
2740
|
function toSidebar(b) {
|
|
2504
|
-
return {
|
|
2741
|
+
return {
|
|
2742
|
+
id: b.id,
|
|
2743
|
+
name: b.name,
|
|
2744
|
+
state: b.state,
|
|
2745
|
+
claudeActivity: b.claudeActivity,
|
|
2746
|
+
sessionTitle: b.claudeSessionTitle,
|
|
2747
|
+
index: b.projectIndex,
|
|
2748
|
+
project: b.projectRoot
|
|
2749
|
+
};
|
|
2505
2750
|
}
|
|
2506
2751
|
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) => {
|
|
2507
2752
|
try {
|
|
@@ -2643,23 +2888,35 @@ var dashboardCommand = new Command7("dashboard").description("Box list + the sel
|
|
|
2643
2888
|
const ep = box.endpoints.endpoints.find((e) => e.kind === kind);
|
|
2644
2889
|
return { name: box.name, url: ep && ep.reachable && ep.url ? ep.url : null };
|
|
2645
2890
|
};
|
|
2891
|
+
const webTarget = (box) => {
|
|
2892
|
+
const ep = box.endpoints.endpoints.find((e) => e.kind === "web");
|
|
2893
|
+
const exposed = Boolean(ep && ep.reachable && ep.url);
|
|
2894
|
+
const url = box.endpoints.domainIsOrb ? `http://${box.endpoints.domain}` : exposed && ep?.url ? ep.url : `http://${box.endpoints.domain}`;
|
|
2895
|
+
return { url, exposed };
|
|
2896
|
+
};
|
|
2646
2897
|
const openVnc = async (boxId) => {
|
|
2647
2898
|
const { url } = await findEndpointUrl(boxId, "vnc");
|
|
2648
2899
|
if (!url) return "VNC not available for this box";
|
|
2900
|
+
let exposedWebUrl = null;
|
|
2649
2901
|
try {
|
|
2650
2902
|
const box = await findBox2(boxId);
|
|
2903
|
+
const web = webTarget(box);
|
|
2904
|
+
if (web.exposed) exposedWebUrl = web.url;
|
|
2651
2905
|
const br = await ensureBoxBrowser(box.container);
|
|
2652
2906
|
if (!br.up) return `VNC: in-box browser unavailable (${br.reason ?? "box not running?"})`;
|
|
2653
2907
|
} catch {
|
|
2654
2908
|
}
|
|
2655
2909
|
detach("open", [url]);
|
|
2910
|
+
if (exposedWebUrl) {
|
|
2911
|
+
detach("open", [exposedWebUrl]);
|
|
2912
|
+
return "Opening VNC + web in browser\u2026";
|
|
2913
|
+
}
|
|
2656
2914
|
return "Opening VNC in browser\u2026";
|
|
2657
2915
|
};
|
|
2658
2916
|
const openWeb = async (boxId) => {
|
|
2659
2917
|
const box = (await listBoxes()).find((b) => b.id === boxId);
|
|
2660
2918
|
if (!box) return "box not found";
|
|
2661
|
-
const
|
|
2662
|
-
const url = ep && ep.reachable && ep.url ? ep.url : `http://${box.endpoints.domain}`;
|
|
2919
|
+
const { url } = webTarget(box);
|
|
2663
2920
|
detach("open", [url]);
|
|
2664
2921
|
return `Opening ${url.replace(/^https?:\/\//, "")}\u2026`;
|
|
2665
2922
|
};
|
|
@@ -2675,6 +2932,12 @@ var dashboardCommand = new Command7("dashboard").description("Box list + the sel
|
|
|
2675
2932
|
if (box.state === "paused") await unpauseBox(box.id);
|
|
2676
2933
|
else await startBox(box.id);
|
|
2677
2934
|
};
|
|
2935
|
+
const pauseBoxAction = async (boxId) => {
|
|
2936
|
+
await pauseBox(boxId);
|
|
2937
|
+
};
|
|
2938
|
+
const stopBoxAction = async (boxId) => {
|
|
2939
|
+
await stopBox(boxId);
|
|
2940
|
+
};
|
|
2678
2941
|
const destroyBoxAction = async (boxId) => {
|
|
2679
2942
|
await destroyBox(boxId);
|
|
2680
2943
|
};
|
|
@@ -2688,6 +2951,8 @@ var dashboardCommand = new Command7("dashboard").description("Box list + the sel
|
|
|
2688
2951
|
openShell,
|
|
2689
2952
|
createNewBox,
|
|
2690
2953
|
resumeBox,
|
|
2954
|
+
pauseBox: pauseBoxAction,
|
|
2955
|
+
stopBox: stopBoxAction,
|
|
2691
2956
|
destroyBox: destroyBoxAction,
|
|
2692
2957
|
openVnc,
|
|
2693
2958
|
openCode,
|
|
@@ -3484,6 +3749,21 @@ var screenCommand = new Command18("screen").description("Open a box's VNC (noVNC
|
|
|
3484
3749
|
}
|
|
3485
3750
|
process.stdout.write(`opened ${url}
|
|
3486
3751
|
`);
|
|
3752
|
+
try {
|
|
3753
|
+
const { record } = await getBoxHostPaths(box.id);
|
|
3754
|
+
const persisted = await readBoxStatus(box.id);
|
|
3755
|
+
const eps = await getBoxEndpoints(record, engine, persisted);
|
|
3756
|
+
const webEp = eps.endpoints.find((e) => e.kind === "web");
|
|
3757
|
+
if (webEp?.reachable && webEp.url) {
|
|
3758
|
+
const webUrl = engine === "orbstack" && !opts.loopback ? `http://${record.container}.orb.local` : webEp.url;
|
|
3759
|
+
const w = spawnSync5("open", [webUrl], { stdio: "inherit" });
|
|
3760
|
+
if (w.status === 0) process.stdout.write(`also opened ${webUrl}
|
|
3761
|
+
`);
|
|
3762
|
+
else log19.warn(`could not open web app (${webUrl})`);
|
|
3763
|
+
}
|
|
3764
|
+
} catch (e) {
|
|
3765
|
+
log19.warn(`could not open web app: ${e instanceof Error ? e.message : String(e)}`);
|
|
3766
|
+
}
|
|
3487
3767
|
} catch (err) {
|
|
3488
3768
|
handleLifecycleError(err);
|
|
3489
3769
|
}
|