@mentiko/pty-mgr 1.3.0 → 1.4.1
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/CHANGELOG.md +10 -0
- package/lib/pty-manager.mjs +218 -35
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.1 - 2026-07-02
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- macOS binaries are now compiled on a macOS runner and ad-hoc code-signed, so
|
|
8
|
+
they carry a valid signature and Apple Silicon (AMFI) no longer SIGKILLs them
|
|
9
|
+
on launch (`killed`). They were previously cross-compiled on Linux, whose
|
|
10
|
+
embedded linker signature is rejected by macOS. The `curl | sh` installer also
|
|
11
|
+
re-signs the binary on download as a fallback for older releases.
|
|
12
|
+
|
|
3
13
|
## 1.3.0 - 2026-07-02
|
|
4
14
|
|
|
5
15
|
### Added
|
package/lib/pty-manager.mjs
CHANGED
|
@@ -38,10 +38,13 @@ import {
|
|
|
38
38
|
readFileSync,
|
|
39
39
|
readdirSync,
|
|
40
40
|
statSync,
|
|
41
|
+
writeFileSync,
|
|
41
42
|
} from "node:fs";
|
|
43
|
+
import { fileURLToPath } from "node:url";
|
|
44
|
+
import { spawn as spawnChild } from "node:child_process";
|
|
42
45
|
|
|
43
46
|
// kept in sync by scripts/version-sync.cjs (rewrites this literal on release)
|
|
44
|
-
export const VERSION = "1.
|
|
47
|
+
export const VERSION = "1.4.1";
|
|
45
48
|
import xterm from "@xterm/headless";
|
|
46
49
|
import { SerializeAddon } from "@xterm/addon-serialize";
|
|
47
50
|
|
|
@@ -210,14 +213,17 @@ class PtySession {
|
|
|
210
213
|
if (!line) { lines.push(""); continue; }
|
|
211
214
|
|
|
212
215
|
let out = "";
|
|
216
|
+
let keep = 0; // output length up to the last cell worth keeping
|
|
213
217
|
let prevFg = -1, prevBg = -1, prevBold = false, prevDim = false;
|
|
214
218
|
let prevItalic = false, prevUnder = false, prevInverse = false;
|
|
215
219
|
|
|
216
220
|
for (let x = 0; x < line.length; x++) {
|
|
217
221
|
const cell = line.getCell(x);
|
|
218
222
|
if (!cell) continue;
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
if (cell.getWidth() === 0) continue; // wide-char continuation cell
|
|
224
|
+
// null cells (never written; TUIs skip them with cursor moves)
|
|
225
|
+
// must still occupy a column, or text collapses together
|
|
226
|
+
const ch = cell.getChars() || " ";
|
|
221
227
|
|
|
222
228
|
const fg = cell.getFgColor();
|
|
223
229
|
const bg = cell.getBgColor();
|
|
@@ -258,8 +264,13 @@ class PtySession {
|
|
|
258
264
|
prevItalic = italic; prevUnder = under; prevInverse = inverse;
|
|
259
265
|
}
|
|
260
266
|
out += ch;
|
|
267
|
+
if (ch !== " " || fgMode !== 0 || bgMode !== 0 ||
|
|
268
|
+
bold || dim || italic || under || inverse) {
|
|
269
|
+
keep = out.length;
|
|
270
|
+
}
|
|
261
271
|
}
|
|
262
|
-
|
|
272
|
+
out = out.slice(0, keep); // drop trailing unstyled blanks
|
|
273
|
+
if (keep > 0 && (prevFg !== -1 || prevBold)) out += "\x1b[0m";
|
|
263
274
|
lines.push(out);
|
|
264
275
|
}
|
|
265
276
|
|
|
@@ -790,15 +801,18 @@ function startDaemon() {
|
|
|
790
801
|
const session = mgr.get(req.name);
|
|
791
802
|
const cols = session.terminal.cols;
|
|
792
803
|
const rows = session.terminal.rows;
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
//
|
|
804
|
+
const alt = session.terminal.buffer.active.type === "alternate";
|
|
805
|
+
// send initial ack with terminal size + buffer state
|
|
806
|
+
conn.write(JSON.stringify({ ok: true, mode: "attach", cols, rows, alt }) + "\n");
|
|
807
|
+
|
|
808
|
+
// replay the session's full terminal state: scrollback, screen,
|
|
809
|
+
// colors, cursor, modes. history lands in the client's own
|
|
810
|
+
// scrollback so it stays scrollable. if the session is inside a
|
|
811
|
+
// TUI, the serializer emits the alt-screen switch itself and the
|
|
812
|
+
// client pops back out on detach.
|
|
813
|
+
conn.write("\x1b[2J\x1b[H" + session.captureAnsi());
|
|
814
|
+
|
|
815
|
+
// nudge the app to repaint so live output aligns with the replay
|
|
802
816
|
if (session.childPid) {
|
|
803
817
|
try { process.kill(session.childPid, "SIGWINCH"); } catch {}
|
|
804
818
|
}
|
|
@@ -812,7 +826,10 @@ function startDaemon() {
|
|
|
812
826
|
// when session exits, notify and close
|
|
813
827
|
const onExit = () => {
|
|
814
828
|
try {
|
|
815
|
-
|
|
829
|
+
if (session.terminal.buffer.active.type === "alternate") {
|
|
830
|
+
conn.write("\x1b[?1049l");
|
|
831
|
+
}
|
|
832
|
+
conn.write("\x1b[0m\r\n[session exited]\r\n");
|
|
816
833
|
conn.end();
|
|
817
834
|
} catch {}
|
|
818
835
|
};
|
|
@@ -1093,6 +1110,7 @@ async function handleCommand(mgr, req, daemonStartedAt, config, tgState = {}) {
|
|
|
1093
1110
|
name: DAEMON_NAME,
|
|
1094
1111
|
pid: process.pid,
|
|
1095
1112
|
socket: SOCKET_PATH,
|
|
1113
|
+
cwd: process.cwd(),
|
|
1096
1114
|
startedAt: daemonStartedAt.toISOString(),
|
|
1097
1115
|
uptimeMs,
|
|
1098
1116
|
uptime: formatUptime(uptimeMs),
|
|
@@ -1147,9 +1165,10 @@ async function handleCommand(mgr, req, daemonStartedAt, config, tgState = {}) {
|
|
|
1147
1165
|
};
|
|
1148
1166
|
mgr.spawn(name, cmdToRun, cmdArgs, opts);
|
|
1149
1167
|
const res = { ok: true, name, pid: mgr.pid(name) };
|
|
1150
|
-
// auto-start logging if --log flag
|
|
1168
|
+
// auto-start logging if --log flag. logs live under the session's own
|
|
1169
|
+
// cwd (client's dir), not the daemon's frozen cwd.
|
|
1151
1170
|
if (args?.log) {
|
|
1152
|
-
const logDir = join(
|
|
1171
|
+
const logDir = join(mgr.get(name).cwd, "agents", "logs");
|
|
1153
1172
|
const logPath = join(logDir, `${name}-${Date.now()}.jsonl`);
|
|
1154
1173
|
mgr.get(name).startLog(logPath, "jsonl");
|
|
1155
1174
|
res.logPath = logPath;
|
|
@@ -1301,10 +1320,11 @@ async function handleCommand(mgr, req, daemonStartedAt, config, tgState = {}) {
|
|
|
1301
1320
|
const path = session.stopLog();
|
|
1302
1321
|
return { ok: true, stopped: true, path };
|
|
1303
1322
|
}
|
|
1304
|
-
// default log dir:
|
|
1323
|
+
// default log dir: <session cwd>/agents/logs/ -- follow the session's
|
|
1324
|
+
// own dir, not the daemon's frozen cwd.
|
|
1305
1325
|
const format = args?.format || "jsonl";
|
|
1306
1326
|
const ext = format === "jsonl" ? "jsonl" : format === "rendered" ? "log" : "raw";
|
|
1307
|
-
const logDir = args?.dir || join(
|
|
1327
|
+
const logDir = args?.dir || join(session.cwd, "agents", "logs");
|
|
1308
1328
|
const ts = Date.now();
|
|
1309
1329
|
const logPath = args?.path || join(logDir, `${name}-${ts}.${ext}`);
|
|
1310
1330
|
session.startLog(logPath, format);
|
|
@@ -1431,8 +1451,12 @@ function sendCommand(req) {
|
|
|
1431
1451
|
function attachToSession(name) {
|
|
1432
1452
|
return new Promise((resolve, reject) => {
|
|
1433
1453
|
const conn = createConnection(SOCKET_PATH);
|
|
1454
|
+
// set on any pre-attach failure so the close handler doesn't run the
|
|
1455
|
+
// detach cleanup (stray "detached" + terminal resets after an error)
|
|
1456
|
+
let failed = false;
|
|
1434
1457
|
|
|
1435
1458
|
conn.on("error", (err) => {
|
|
1459
|
+
failed = true;
|
|
1436
1460
|
if (err.code === "ENOENT" || err.code === "ECONNREFUSED") {
|
|
1437
1461
|
reject(new Error("daemon not running. start with: pty-mgr daemon"));
|
|
1438
1462
|
} else {
|
|
@@ -1447,6 +1471,15 @@ function attachToSession(name) {
|
|
|
1447
1471
|
let gotAck = false;
|
|
1448
1472
|
let headerBuf = "";
|
|
1449
1473
|
|
|
1474
|
+
// last-seen alt-screen state: detach pops the client back to its
|
|
1475
|
+
// normal screen only if the session left it in the alternate buffer
|
|
1476
|
+
let altActive = false;
|
|
1477
|
+
const trackAlt = (chunk) => {
|
|
1478
|
+
const h = chunk.lastIndexOf("\x1b[?1049h");
|
|
1479
|
+
const l = chunk.lastIndexOf("\x1b[?1049l");
|
|
1480
|
+
if (h !== -1 || l !== -1) altActive = h > l;
|
|
1481
|
+
};
|
|
1482
|
+
|
|
1450
1483
|
conn.on("data", (data) => {
|
|
1451
1484
|
if (!gotAck) {
|
|
1452
1485
|
// first line is the JSON ack
|
|
@@ -1461,19 +1494,20 @@ function attachToSession(name) {
|
|
|
1461
1494
|
try {
|
|
1462
1495
|
ack = JSON.parse(ackStr);
|
|
1463
1496
|
if (!ack.ok) {
|
|
1464
|
-
|
|
1497
|
+
failed = true;
|
|
1465
1498
|
conn.end();
|
|
1466
1499
|
reject(new Error(ack.error));
|
|
1467
1500
|
return;
|
|
1468
1501
|
}
|
|
1469
1502
|
} catch {
|
|
1470
|
-
|
|
1503
|
+
failed = true;
|
|
1471
1504
|
conn.end();
|
|
1472
|
-
reject(new Error("bad ack"));
|
|
1505
|
+
reject(new Error("bad ack from daemon"));
|
|
1473
1506
|
return;
|
|
1474
1507
|
}
|
|
1475
1508
|
|
|
1476
1509
|
gotAck = true;
|
|
1510
|
+
altActive = !!ack.alt;
|
|
1477
1511
|
|
|
1478
1512
|
// resize client terminal to match session
|
|
1479
1513
|
if (ack.cols && ack.rows) {
|
|
@@ -1489,6 +1523,7 @@ function attachToSession(name) {
|
|
|
1489
1523
|
|
|
1490
1524
|
// write any remaining data after the ack
|
|
1491
1525
|
if (remainder) {
|
|
1526
|
+
trackAlt(remainder);
|
|
1492
1527
|
process.stdout.write(remainder);
|
|
1493
1528
|
}
|
|
1494
1529
|
|
|
@@ -1506,6 +1541,7 @@ function attachToSession(name) {
|
|
|
1506
1541
|
}
|
|
1507
1542
|
|
|
1508
1543
|
// streaming mode: write pty output to terminal
|
|
1544
|
+
trackAlt(data);
|
|
1509
1545
|
process.stdout.write(data);
|
|
1510
1546
|
});
|
|
1511
1547
|
|
|
@@ -1515,15 +1551,22 @@ function attachToSession(name) {
|
|
|
1515
1551
|
|
|
1516
1552
|
let detached = false;
|
|
1517
1553
|
function detach() {
|
|
1518
|
-
if (detached) return;
|
|
1554
|
+
if (detached || failed) return;
|
|
1519
1555
|
detached = true;
|
|
1520
1556
|
if (process.stdin.isRaw) {
|
|
1521
1557
|
process.stdin.setRawMode(false);
|
|
1522
1558
|
process.stdin.pause();
|
|
1523
1559
|
process.stdin.removeAllListeners("data");
|
|
1524
1560
|
}
|
|
1561
|
+
// pop the alt screen only if the session left us in one, then
|
|
1562
|
+
// reset SGR/cursor and any modes the replayed TUI enabled
|
|
1563
|
+
// (keypad, cursor keys, bracketed paste, mouse tracking)
|
|
1564
|
+
if (altActive) process.stdout.write("\x1b[?1049l");
|
|
1565
|
+
process.stdout.write(
|
|
1566
|
+
"\x1b[0m\x1b[?25h\x1b>\x1b[?1l\x1b[?2004l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1004l\x1b[?9l"
|
|
1567
|
+
);
|
|
1525
1568
|
conn.end();
|
|
1526
|
-
console.log("
|
|
1569
|
+
console.log("detached");
|
|
1527
1570
|
resolve();
|
|
1528
1571
|
}
|
|
1529
1572
|
});
|
|
@@ -1569,6 +1612,8 @@ usage:
|
|
|
1569
1612
|
p flow list [--verbose] [--config file] list configured agent workflows
|
|
1570
1613
|
p flow show <name> [--config file] show one configured agent workflow
|
|
1571
1614
|
p flow run <name> --task <text> run a configured agent workflow
|
|
1615
|
+
p flow new [name] [--global] scaffold an example flow (project cwd, or --global user config)
|
|
1616
|
+
p open config [editor] open config dir in editor (--local project, --defaults packaged)
|
|
1572
1617
|
p demo run self-test (no daemon needed)
|
|
1573
1618
|
p tg <message> send telegram notification
|
|
1574
1619
|
p tg <message> --reply send message and wait for reply (blocking)
|
|
@@ -1967,6 +2012,104 @@ export function loadFlowConfig(configPath = "pty-mgr.config.json") {
|
|
|
1967
2012
|
return { adapters: {}, flows: {} };
|
|
1968
2013
|
}
|
|
1969
2014
|
|
|
2015
|
+
function xdgConfigHome() {
|
|
2016
|
+
return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
export function userConfigPath() {
|
|
2020
|
+
return process.env.PTY_MGR_CONFIG || join(xdgConfigHome(), "pty-mgr", "config.json");
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
function packagedDefaultsConfigPath() {
|
|
2024
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "pty-mgr.config.json");
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
export function findProjectConfigPath(cwd = process.cwd()) {
|
|
2028
|
+
let dir = cwd;
|
|
2029
|
+
const home = homedir();
|
|
2030
|
+
while (true) {
|
|
2031
|
+
const candidate = join(dir, "pty-mgr.config.json");
|
|
2032
|
+
if (existsSync(candidate)) return candidate;
|
|
2033
|
+
if (existsSync(join(dir, ".git")) || dir === home) return null;
|
|
2034
|
+
const parent = dirname(dir);
|
|
2035
|
+
if (parent === dir) return null;
|
|
2036
|
+
dir = parent;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// merge flow configs low->high precedence; higher layers override by top-level key.
|
|
2041
|
+
// returns { adapters, flows, scopes } where scopes maps flow name -> layer it came from.
|
|
2042
|
+
export function resolveMergedConfig({ cwd = process.cwd(), explicitPath = null } = {}) {
|
|
2043
|
+
if (explicitPath) {
|
|
2044
|
+
const config = loadFlowConfig(explicitPath);
|
|
2045
|
+
const scopes = {};
|
|
2046
|
+
for (const name of Object.keys(config.flows || {})) scopes[name] = "config";
|
|
2047
|
+
return { adapters: config.adapters || {}, flows: config.flows || {}, scopes };
|
|
2048
|
+
}
|
|
2049
|
+
const layers = [];
|
|
2050
|
+
const defaultsPath = packagedDefaultsConfigPath();
|
|
2051
|
+
if (existsSync(defaultsPath)) layers.push(["default", loadFlowConfig(defaultsPath)]);
|
|
2052
|
+
const userPath = userConfigPath();
|
|
2053
|
+
if (existsSync(userPath)) layers.push(["user", loadFlowConfig(userPath)]);
|
|
2054
|
+
const projectPath = findProjectConfigPath(cwd);
|
|
2055
|
+
if (projectPath && projectPath !== defaultsPath) layers.push(["project", loadFlowConfig(projectPath)]);
|
|
2056
|
+
|
|
2057
|
+
const adapters = {};
|
|
2058
|
+
const flows = {};
|
|
2059
|
+
const scopes = {};
|
|
2060
|
+
for (const [scope, config] of layers) {
|
|
2061
|
+
Object.assign(adapters, config.adapters || {});
|
|
2062
|
+
for (const [name, flow] of Object.entries(config.flows || {})) {
|
|
2063
|
+
flows[name] = flow;
|
|
2064
|
+
scopes[name] = scope;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
return { adapters, flows, scopes };
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
function exampleFlow() {
|
|
2071
|
+
return {
|
|
2072
|
+
agents: {
|
|
2073
|
+
writer: { kind: "codex", base: "flow-writer" },
|
|
2074
|
+
reviewer: { kind: "claude", base: "flow-reviewer" },
|
|
2075
|
+
},
|
|
2076
|
+
start: { to: "writer", template: "{task}" },
|
|
2077
|
+
turns: [
|
|
2078
|
+
{
|
|
2079
|
+
from: "writer",
|
|
2080
|
+
to: "reviewer",
|
|
2081
|
+
append:
|
|
2082
|
+
"Review what the other agent just did in this repository. Give specific, actionable feedback as a short numbered list, each item referencing file:line.",
|
|
2083
|
+
},
|
|
2084
|
+
{
|
|
2085
|
+
from: "reviewer",
|
|
2086
|
+
to: "writer",
|
|
2087
|
+
append:
|
|
2088
|
+
"Apply the feedback you agree with, note anything you skip and why, then report the final state. Original task: {goal}",
|
|
2089
|
+
},
|
|
2090
|
+
],
|
|
2091
|
+
maxCycles: 1,
|
|
2092
|
+
watchInterval: "10s",
|
|
2093
|
+
settleMs: 1500,
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
function scaffoldFlowConfig(targetPath, name) {
|
|
2098
|
+
const dir = dirname(targetPath);
|
|
2099
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
2100
|
+
const config = existsSync(targetPath) ? loadFlowConfig(targetPath) : { flows: {} };
|
|
2101
|
+
if (!config.flows) config.flows = {};
|
|
2102
|
+
if (config.flows[name]) {
|
|
2103
|
+
console.error(`flow already exists: ${name} (${targetPath})`);
|
|
2104
|
+
process.exit(1);
|
|
2105
|
+
}
|
|
2106
|
+
config.flows[name] = exampleFlow();
|
|
2107
|
+
writeFileSync(targetPath, JSON.stringify(config, null, 2) + "\n");
|
|
2108
|
+
console.log(`created flow "${name}" in ${targetPath}`);
|
|
2109
|
+
console.log(` edit: p open config${targetPath === userConfigPath() ? "" : " --local"}`);
|
|
2110
|
+
console.log(` run: p flow run ${name} --task "..."`);
|
|
2111
|
+
}
|
|
2112
|
+
|
|
1970
2113
|
function walkJsonlFiles(root, files = []) {
|
|
1971
2114
|
root = expandHome(root);
|
|
1972
2115
|
if (!root || !existsSync(root)) return files;
|
|
@@ -2650,20 +2793,55 @@ async function cli() {
|
|
|
2650
2793
|
return;
|
|
2651
2794
|
}
|
|
2652
2795
|
|
|
2796
|
+
if (command === "open") {
|
|
2797
|
+
const sub = args[0];
|
|
2798
|
+
if (sub !== "config") {
|
|
2799
|
+
console.error("usage: p open config [editor] [--local|--defaults]");
|
|
2800
|
+
process.exit(1);
|
|
2801
|
+
}
|
|
2802
|
+
const { flags: openFlags, rest: openRest } = parseFlagArgs(args, 1);
|
|
2803
|
+
const editor = openRest[0] || process.env.VISUAL || process.env.EDITOR || "code";
|
|
2804
|
+
let dir;
|
|
2805
|
+
if (openFlags.defaults) {
|
|
2806
|
+
dir = dirname(packagedDefaultsConfigPath());
|
|
2807
|
+
} else if (openFlags.local) {
|
|
2808
|
+
const projectPath = findProjectConfigPath(process.cwd());
|
|
2809
|
+
dir = projectPath ? dirname(projectPath) : process.cwd();
|
|
2810
|
+
} else {
|
|
2811
|
+
dir = dirname(userConfigPath());
|
|
2812
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
2813
|
+
}
|
|
2814
|
+
console.log(`opening ${dir} with ${editor}`);
|
|
2815
|
+
await new Promise((resolve) => {
|
|
2816
|
+
const child = spawnChild(editor, [dir], { stdio: "inherit" });
|
|
2817
|
+
child.on("error", (err) => {
|
|
2818
|
+
console.error(`could not launch ${editor}: ${err.message}`);
|
|
2819
|
+
resolve();
|
|
2820
|
+
});
|
|
2821
|
+
child.on("exit", () => resolve());
|
|
2822
|
+
});
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2653
2826
|
if (command === "flow") {
|
|
2654
2827
|
const subcommand = args[0];
|
|
2655
|
-
const { flags } = parseFlagArgs(args, 1);
|
|
2656
|
-
|
|
2828
|
+
const { flags, rest } = parseFlagArgs(args, 1);
|
|
2829
|
+
if (subcommand === "new") {
|
|
2830
|
+
const name = rest[0] || "example";
|
|
2831
|
+
const target = flags.global ? userConfigPath() : join(process.cwd(), "pty-mgr.config.json");
|
|
2832
|
+
scaffoldFlowConfig(target, name);
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2657
2835
|
if (subcommand === "list") {
|
|
2658
|
-
const
|
|
2659
|
-
const names = Object.keys(
|
|
2836
|
+
const { flows, scopes } = resolveMergedConfig({ cwd: process.cwd(), explicitPath: flags.config });
|
|
2837
|
+
const names = Object.keys(flows);
|
|
2660
2838
|
if (names.length === 0) {
|
|
2661
2839
|
console.log("no flows configured");
|
|
2662
2840
|
} else {
|
|
2663
2841
|
for (const name of names) {
|
|
2664
|
-
console.log(name);
|
|
2842
|
+
console.log(flags.config ? name : `${name} [${scopes[name]}]`);
|
|
2665
2843
|
if (flags.verbose) {
|
|
2666
|
-
const flow =
|
|
2844
|
+
const flow = flows[name] || {};
|
|
2667
2845
|
for (const [alias, agent] of Object.entries(flow.agents || {})) {
|
|
2668
2846
|
console.log(` ${alias} -> ${flowAgentKind(agent)}`);
|
|
2669
2847
|
}
|
|
@@ -2680,8 +2858,8 @@ async function cli() {
|
|
|
2680
2858
|
console.error("usage: pty-mgr flow show <name>");
|
|
2681
2859
|
process.exit(1);
|
|
2682
2860
|
}
|
|
2683
|
-
const
|
|
2684
|
-
const flow =
|
|
2861
|
+
const { flows } = resolveMergedConfig({ cwd: process.cwd(), explicitPath: flags.config });
|
|
2862
|
+
const flow = flows[name];
|
|
2685
2863
|
if (!flow) {
|
|
2686
2864
|
console.error(`flow not found: ${name}`);
|
|
2687
2865
|
process.exit(1);
|
|
@@ -2716,19 +2894,20 @@ async function cli() {
|
|
|
2716
2894
|
console.error("flow run requires --task <text>");
|
|
2717
2895
|
process.exit(1);
|
|
2718
2896
|
}
|
|
2897
|
+
const runCwd = runFlags.cwd || process.cwd();
|
|
2898
|
+
const merged = resolveMergedConfig({ cwd: runCwd, explicitPath: runFlags.config || flags.config });
|
|
2719
2899
|
try {
|
|
2720
2900
|
const result = await runFlowWorkflow({
|
|
2721
2901
|
workflow,
|
|
2722
2902
|
task,
|
|
2723
2903
|
goal: runFlags.goal,
|
|
2724
|
-
|
|
2725
|
-
cwd: runFlags.cwd || process.cwd(),
|
|
2904
|
+
cwd: runCwd,
|
|
2726
2905
|
maxCycles: runFlags["max-cycles"],
|
|
2727
2906
|
watchInterval: runFlags["watch-interval"],
|
|
2728
2907
|
settleMs: runFlags["settle-ms"],
|
|
2729
2908
|
timeoutMs: runFlags["timeout-ms"],
|
|
2730
2909
|
intervalMs: runFlags["interval-ms"],
|
|
2731
|
-
});
|
|
2910
|
+
}, { config: merged });
|
|
2732
2911
|
console.log(JSON.stringify(result, null, 2));
|
|
2733
2912
|
} catch (err) {
|
|
2734
2913
|
console.error(err.message);
|
|
@@ -3031,6 +3210,7 @@ async function cli() {
|
|
|
3031
3210
|
console.log(`pty-manager daemon (${st.name})`);
|
|
3032
3211
|
console.log(` pid: ${st.pid}`);
|
|
3033
3212
|
console.log(` socket: ${st.socket}`);
|
|
3213
|
+
console.log(` cwd: ${st.cwd}`);
|
|
3034
3214
|
console.log(` uptime: ${st.uptime}`);
|
|
3035
3215
|
console.log(` sessions: ${st.sessions.alive} alive, ${st.sessions.dead} dead, ${st.sessions.total} total`);
|
|
3036
3216
|
console.log(` screen: ${st.config.cols}x${st.config.rows}`);
|
|
@@ -3119,7 +3299,10 @@ const isMain = _isBunCompiled || (_basename1 && _pat.test(_basename1)) || (_base
|
|
|
3119
3299
|
|
|
3120
3300
|
if (isMain) {
|
|
3121
3301
|
cli().catch((err) => {
|
|
3122
|
-
|
|
3302
|
+
// message only -- a raw error object prints a stack trace for what are
|
|
3303
|
+
// usually plain user-facing failures (session not found, daemon down)
|
|
3304
|
+
console.error("error:", err.message);
|
|
3305
|
+
if (process.env.PTY_MGR_DEBUG) console.error(err.stack);
|
|
3123
3306
|
process.exit(1);
|
|
3124
3307
|
});
|
|
3125
3308
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentiko/pty-mgr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "PTY session manager with terminal emulation for programmatic session control.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/pty-manager.mjs",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"@xterm/headless": "^6.0.0"
|
|
27
27
|
},
|
|
28
28
|
"optionalDependencies": {
|
|
29
|
-
"@mentiko/pty-mgr-linux-x64": "1.
|
|
30
|
-
"@mentiko/pty-mgr-linux-arm64": "1.
|
|
31
|
-
"@mentiko/pty-mgr-darwin-x64": "1.
|
|
32
|
-
"@mentiko/pty-mgr-darwin-arm64": "1.
|
|
29
|
+
"@mentiko/pty-mgr-linux-x64": "1.4.1",
|
|
30
|
+
"@mentiko/pty-mgr-linux-arm64": "1.4.1",
|
|
31
|
+
"@mentiko/pty-mgr-darwin-x64": "1.4.1",
|
|
32
|
+
"@mentiko/pty-mgr-darwin-arm64": "1.4.1"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"bun": ">=1.0"
|