muuuuse 2.3.2 → 2.3.4
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 +5 -2
- package/package.json +1 -1
- package/src/cli.js +35 -25
- package/src/runtime.js +92 -18
- package/src/util.js +21 -14
package/README.md
CHANGED
|
@@ -15,13 +15,16 @@ It does one job:
|
|
|
15
15
|
The whole surface is:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
+
muuuuse
|
|
18
19
|
muuuuse 1
|
|
19
20
|
muuuuse 1 flow on
|
|
20
21
|
muuuuse 1 flow off
|
|
21
22
|
muuuuse 1 flow off continue 5
|
|
23
|
+
muuuuse 1 flow on continue 3 5 6
|
|
22
24
|
muuuuse 2
|
|
23
25
|
muuuuse 2 flow off
|
|
24
26
|
muuuuse 2 flow on continue 3
|
|
27
|
+
muuuuse 2 flow on continue 3 5 6
|
|
25
28
|
muuuuse 3
|
|
26
29
|
muuuuse 3 flow on
|
|
27
30
|
muuuuse 4
|
|
@@ -48,7 +51,7 @@ Now both shells are armed. `muuuuse 1` generates the session key, `muuuuse 2` si
|
|
|
48
51
|
|
|
49
52
|
`flow on` means that seat relays commentary and final answers. `flow off` means that seat relays and accepts final answers only. Mixed calibration is allowed per seat.
|
|
50
53
|
|
|
51
|
-
`continue <seat
|
|
54
|
+
`continue <seat> [<seat> ...]` forwards that seat's relayed output into one or more additional armed seats without changing the signed odd/even pair law. This lets you build local loops like `1 -> 2 -> 3 -> 4 -> 1`, split chains like `2 -> 3` and `2 -> 5`, or wider local meshes while every adjacent pair still keeps its own session keypair.
|
|
52
55
|
|
|
53
56
|
If you want Codex in one and Gemini in the other, start them inside the armed shells:
|
|
54
57
|
|
|
@@ -78,7 +81,7 @@ muuuuse stop
|
|
|
78
81
|
|
|
79
82
|
- state lives under `~/.muuuuse`
|
|
80
83
|
- only the signed armed pair can exchange relay events
|
|
81
|
-
- `continue <seat
|
|
84
|
+
- `continue <seat> [<seat> ...]` is a separate local forwarding lane and can target any armed seat numbers
|
|
82
85
|
- supported relay detection is built for Codex, Claude, and Gemini
|
|
83
86
|
|
|
84
87
|
## Install
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -56,9 +56,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
56
56
|
|
|
57
57
|
const seatId = normalizeSeatId(command);
|
|
58
58
|
if (seatId) {
|
|
59
|
-
const { flowMode, continueSeatId } = parseSeatOptions(command, argv.slice(1));
|
|
59
|
+
const { flowMode, continueSeatId, continueSeatIds } = parseSeatOptions(command, argv.slice(1));
|
|
60
60
|
const seat = new ArmedSeat({
|
|
61
61
|
cwd: process.cwd(),
|
|
62
|
+
continueSeatIds,
|
|
62
63
|
continueSeatId,
|
|
63
64
|
flowMode,
|
|
64
65
|
seatId,
|
|
@@ -83,8 +84,11 @@ function renderSeatStatus(seat) {
|
|
|
83
84
|
if (seat.partnerLive) {
|
|
84
85
|
bits.push("peer live");
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
const continueSeatIds = Array.isArray(seat.continueSeatIds)
|
|
88
|
+
? seat.continueSeatIds
|
|
89
|
+
: (seat.continueSeatId ? [seat.continueSeatId] : []);
|
|
90
|
+
if (continueSeatIds.length > 0) {
|
|
91
|
+
bits.push(`continue ${continueSeatIds.join(",")}`);
|
|
88
92
|
}
|
|
89
93
|
if (seat.trust) {
|
|
90
94
|
bits.push(`trust ${seat.trust}`);
|
|
@@ -105,12 +109,14 @@ function renderSeatStatus(seat) {
|
|
|
105
109
|
|
|
106
110
|
function parseSeatOptions(command, args) {
|
|
107
111
|
let flowMode = "off";
|
|
108
|
-
|
|
112
|
+
const continueSeatIds = [];
|
|
113
|
+
let sawContinue = false;
|
|
114
|
+
let index = 0;
|
|
109
115
|
|
|
110
|
-
|
|
116
|
+
while (index < args.length) {
|
|
111
117
|
const token = String(args[index] || "").trim().toLowerCase();
|
|
112
118
|
|
|
113
|
-
if (token === "flow") {
|
|
119
|
+
if (token === "flow" && !sawContinue) {
|
|
114
120
|
const flowToken = String(args[index + 1] || "").trim().toLowerCase();
|
|
115
121
|
if (flowToken === "on" || flowToken === "off") {
|
|
116
122
|
flowMode = flowToken;
|
|
@@ -121,10 +127,22 @@ function parseSeatOptions(command, args) {
|
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
if (token === "continue") {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
sawContinue = true;
|
|
131
|
+
let consumedTargets = 0;
|
|
132
|
+
index += 1;
|
|
133
|
+
|
|
134
|
+
while (index < args.length) {
|
|
135
|
+
const targetSeatId = normalizeSeatId(args[index]);
|
|
136
|
+
if (!targetSeatId) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
continueSeatIds.push(targetSeatId);
|
|
141
|
+
consumedTargets += 1;
|
|
142
|
+
index += 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (consumedTargets > 0) {
|
|
128
146
|
continue;
|
|
129
147
|
}
|
|
130
148
|
break;
|
|
@@ -133,27 +151,19 @@ function parseSeatOptions(command, args) {
|
|
|
133
151
|
break;
|
|
134
152
|
}
|
|
135
153
|
|
|
136
|
-
if (
|
|
137
|
-
return {
|
|
154
|
+
if (index === args.length) {
|
|
155
|
+
return {
|
|
156
|
+
flowMode,
|
|
157
|
+
continueSeatId: continueSeatIds[0] || null,
|
|
158
|
+
continueSeatIds,
|
|
159
|
+
};
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
throw new Error(
|
|
141
|
-
`\`muuuuse ${command}\` accepts no extra arguments, \`flow on\` / \`flow off\`, optional \`continue <seat
|
|
163
|
+
`\`muuuuse ${command}\` accepts no extra arguments, \`flow on\` / \`flow off\`, optional \`continue <seat> [<seat> ...]\`, or both in sequence. Run it directly in the terminal you want to arm.`
|
|
142
164
|
);
|
|
143
165
|
}
|
|
144
166
|
|
|
145
|
-
function consumedAllArgs(args, flowMode, continueSeatId) {
|
|
146
|
-
const expected = [];
|
|
147
|
-
if (flowMode !== "off" || args.includes("flow")) {
|
|
148
|
-
expected.push("flow", flowMode);
|
|
149
|
-
}
|
|
150
|
-
if (continueSeatId !== null || args.includes("continue")) {
|
|
151
|
-
expected.push("continue", String(continueSeatId));
|
|
152
|
-
}
|
|
153
|
-
return expected.length === args.length &&
|
|
154
|
-
expected.every((value, index) => String(args[index]).trim().toLowerCase() === String(value).trim().toLowerCase());
|
|
155
|
-
}
|
|
156
|
-
|
|
157
167
|
module.exports = {
|
|
158
168
|
main,
|
|
159
169
|
};
|
package/src/runtime.js
CHANGED
|
@@ -56,6 +56,32 @@ const CHILD_ENV_DROP_KEYS = [
|
|
|
56
56
|
"CODEX_THREAD_ID",
|
|
57
57
|
];
|
|
58
58
|
|
|
59
|
+
function bestEffortEnableChildEcho(child) {
|
|
60
|
+
const ptsName = String(child?.ptsName || "").trim();
|
|
61
|
+
if (!ptsName || process.platform === "win32") {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
execFileSync("stty", [
|
|
67
|
+
"-F",
|
|
68
|
+
ptsName,
|
|
69
|
+
"echo",
|
|
70
|
+
"icanon",
|
|
71
|
+
"isig",
|
|
72
|
+
"iexten",
|
|
73
|
+
"echoe",
|
|
74
|
+
"echok",
|
|
75
|
+
"echoke",
|
|
76
|
+
"echoctl",
|
|
77
|
+
], {
|
|
78
|
+
stdio: "ignore",
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
// Best effort only. The shell or child app may later change its own tty mode.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
59
85
|
function normalizeFlowMode(flowMode) {
|
|
60
86
|
return String(flowMode || "").trim().toLowerCase() === "on" ? "on" : "off";
|
|
61
87
|
}
|
|
@@ -65,6 +91,36 @@ function normalizeContinueSeatId(value) {
|
|
|
65
91
|
return seatId || null;
|
|
66
92
|
}
|
|
67
93
|
|
|
94
|
+
function normalizeContinueSeatIds(value) {
|
|
95
|
+
const values = Array.isArray(value) ? value : [value];
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
const seatIds = [];
|
|
98
|
+
|
|
99
|
+
for (const entry of values) {
|
|
100
|
+
const seatId = normalizeContinueSeatId(entry);
|
|
101
|
+
if (!seatId || seen.has(seatId)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
seen.add(seatId);
|
|
106
|
+
seatIds.push(seatId);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return seatIds;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveContinueSeatConfig(continueSeatIds, continueSeatId) {
|
|
113
|
+
if (Array.isArray(continueSeatIds) && continueSeatIds.length > 0) {
|
|
114
|
+
return continueSeatIds;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (continueSeatIds !== undefined && continueSeatIds !== null && !Array.isArray(continueSeatIds)) {
|
|
118
|
+
return continueSeatIds;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return continueSeatId;
|
|
122
|
+
}
|
|
123
|
+
|
|
68
124
|
function resolveShell() {
|
|
69
125
|
const shell = String(process.env.SHELL || "").trim();
|
|
70
126
|
return shell || "/bin/bash";
|
|
@@ -587,9 +643,12 @@ class ArmedSeat {
|
|
|
587
643
|
this.partnerSeatId = getPartnerSeatId(options.seatId);
|
|
588
644
|
this.anchorSeatId = isAnchorSeat(options.seatId) ? options.seatId : this.partnerSeatId;
|
|
589
645
|
this.flowMode = normalizeFlowMode(options.flowMode);
|
|
590
|
-
this.
|
|
646
|
+
this.continueSeatIds = normalizeContinueSeatIds(
|
|
647
|
+
resolveContinueSeatConfig(options.continueSeatIds, options.continueSeatId)
|
|
648
|
+
);
|
|
649
|
+
this.continueSeatId = this.continueSeatIds[0] || null;
|
|
591
650
|
this.cwd = normalizeWorkingPath(options.cwd);
|
|
592
|
-
if (this.
|
|
651
|
+
if (this.continueSeatIds.includes(this.seatId)) {
|
|
593
652
|
throw new Error(`\`muuuuse ${this.seatId}\` cannot continue to itself.`);
|
|
594
653
|
}
|
|
595
654
|
this.sessionName = resolveSessionName(this.cwd, this.seatId);
|
|
@@ -665,6 +724,7 @@ class ArmedSeat {
|
|
|
665
724
|
partnerSeatId: this.partnerSeatId,
|
|
666
725
|
sessionName: this.sessionName,
|
|
667
726
|
flowMode: this.flowMode,
|
|
727
|
+
continueSeatIds: this.continueSeatIds,
|
|
668
728
|
continueSeatId: this.continueSeatId,
|
|
669
729
|
cwd: this.cwd,
|
|
670
730
|
pid: process.pid,
|
|
@@ -681,6 +741,7 @@ class ArmedSeat {
|
|
|
681
741
|
partnerSeatId: this.partnerSeatId,
|
|
682
742
|
sessionName: this.sessionName,
|
|
683
743
|
flowMode: this.flowMode,
|
|
744
|
+
continueSeatIds: this.continueSeatIds,
|
|
684
745
|
continueSeatId: this.continueSeatId,
|
|
685
746
|
cwd: this.cwd,
|
|
686
747
|
pid: process.pid,
|
|
@@ -874,6 +935,7 @@ class ArmedSeat {
|
|
|
874
935
|
env: childEnv,
|
|
875
936
|
name: childEnv.TERM,
|
|
876
937
|
});
|
|
938
|
+
bestEffortEnableChildEcho(this.child);
|
|
877
939
|
|
|
878
940
|
this.childPid = this.child.pid;
|
|
879
941
|
this.writeMeta();
|
|
@@ -1026,18 +1088,19 @@ class ArmedSeat {
|
|
|
1026
1088
|
return Number.isFinite(requestedAtMs) && requestedAtMs > this.startedAtMs;
|
|
1027
1089
|
}
|
|
1028
1090
|
|
|
1029
|
-
findContinuationTarget() {
|
|
1030
|
-
|
|
1091
|
+
findContinuationTarget(targetSeatId) {
|
|
1092
|
+
const normalizedTargetSeatId = normalizeContinueSeatId(targetSeatId);
|
|
1093
|
+
if (!normalizedTargetSeatId) {
|
|
1031
1094
|
return null;
|
|
1032
1095
|
}
|
|
1033
1096
|
|
|
1034
1097
|
const candidates = listSessionNames()
|
|
1035
1098
|
.map((sessionName) => {
|
|
1036
|
-
if (!getSeatDirIfExists(sessionName,
|
|
1099
|
+
if (!getSeatDirIfExists(sessionName, normalizedTargetSeatId)) {
|
|
1037
1100
|
return null;
|
|
1038
1101
|
}
|
|
1039
1102
|
|
|
1040
|
-
const seat = buildSeatReport(sessionName,
|
|
1103
|
+
const seat = buildSeatReport(sessionName, normalizedTargetSeatId);
|
|
1041
1104
|
if (!seat || !matchesWorkingPath(seat.cwd, this.cwd)) {
|
|
1042
1105
|
return null;
|
|
1043
1106
|
}
|
|
@@ -1457,19 +1520,21 @@ class ArmedSeat {
|
|
|
1457
1520
|
}
|
|
1458
1521
|
|
|
1459
1522
|
forwardContinuation(signedEntry) {
|
|
1460
|
-
if (
|
|
1523
|
+
if (this.continueSeatIds.length === 0) {
|
|
1461
1524
|
return;
|
|
1462
1525
|
}
|
|
1463
1526
|
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1527
|
+
for (const continueSeatId of this.continueSeatIds) {
|
|
1528
|
+
const target = this.findContinuationTarget(continueSeatId);
|
|
1529
|
+
if (!target) {
|
|
1530
|
+
this.log(`[${this.seatId}] continue ${continueSeatId} unavailable`);
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1469
1533
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1534
|
+
const continuationEntry = buildContinuationEntry(this.sessionName, target.seatId, signedEntry);
|
|
1535
|
+
appendJsonl(target.paths.continuePath, continuationEntry);
|
|
1536
|
+
this.log(`[${this.seatId} => ${target.seatId}] ${previewText(continuationEntry.text)}`);
|
|
1537
|
+
}
|
|
1473
1538
|
}
|
|
1474
1539
|
|
|
1475
1540
|
async tick() {
|
|
@@ -1518,8 +1583,9 @@ class ArmedSeat {
|
|
|
1518
1583
|
this.log(`${BRAND} seat ${this.seatId} armed for ${this.sessionName}.`);
|
|
1519
1584
|
this.log("Use this shell normally. Codex, Claude, and Gemini relay automatically from their local session logs.");
|
|
1520
1585
|
this.log(`Seat ${this.seatId} relay mode is flow ${this.flowMode}.`);
|
|
1521
|
-
if (this.
|
|
1522
|
-
|
|
1586
|
+
if (this.continueSeatIds.length > 0) {
|
|
1587
|
+
const targetLabel = this.continueSeatIds.length === 1 ? "seat" : "seats";
|
|
1588
|
+
this.log(`Seat ${this.seatId} continues to ${targetLabel} ${this.continueSeatIds.join(", ")}.`);
|
|
1523
1589
|
}
|
|
1524
1590
|
if (isAnchorSeat(this.seatId)) {
|
|
1525
1591
|
this.log(`Seat ${this.seatId} generated the session key and is waiting for seat ${this.partnerSeatId} to sign it.`);
|
|
@@ -1618,11 +1684,19 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1618
1684
|
return null;
|
|
1619
1685
|
}
|
|
1620
1686
|
|
|
1687
|
+
const continueSeatIds = normalizeContinueSeatIds(
|
|
1688
|
+
resolveContinueSeatConfig(
|
|
1689
|
+
status?.continueSeatIds ?? meta?.continueSeatIds,
|
|
1690
|
+
status?.continueSeatId ?? meta?.continueSeatId
|
|
1691
|
+
)
|
|
1692
|
+
);
|
|
1693
|
+
|
|
1621
1694
|
return {
|
|
1622
1695
|
seatId,
|
|
1623
1696
|
state: wrapperLive ? status?.state || "running" : "orphaned_child",
|
|
1624
1697
|
flowMode: status?.flowMode || meta?.flowMode || "off",
|
|
1625
|
-
continueSeatId:
|
|
1698
|
+
continueSeatId: continueSeatIds[0] || null,
|
|
1699
|
+
continueSeatIds,
|
|
1626
1700
|
wrapperPid,
|
|
1627
1701
|
childPid,
|
|
1628
1702
|
wrapperLive,
|
package/src/util.js
CHANGED
|
@@ -284,20 +284,27 @@ function usage() {
|
|
|
284
284
|
return [
|
|
285
285
|
`${BRAND} arms regular terminals in isolated odd/even pairs and relays assistant output between each pair.`,
|
|
286
286
|
"",
|
|
287
|
-
"
|
|
288
|
-
" muuuuse
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
" muuuuse
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
-
" muuuuse
|
|
295
|
-
"
|
|
296
|
-
"
|
|
297
|
-
" muuuuse
|
|
298
|
-
"
|
|
299
|
-
"
|
|
287
|
+
"Command List:",
|
|
288
|
+
" muuuuse",
|
|
289
|
+
" Show this command list.",
|
|
290
|
+
"",
|
|
291
|
+
" muuuuse <seat>",
|
|
292
|
+
" Arm a seat and keep its normal odd/even partner relay behavior.",
|
|
293
|
+
"",
|
|
294
|
+
" muuuuse <seat> flow on",
|
|
295
|
+
" Relay commentary and final answers into that armed shell.",
|
|
296
|
+
"",
|
|
297
|
+
" muuuuse <seat> flow off",
|
|
298
|
+
" Relay final answers only into that armed shell.",
|
|
299
|
+
"",
|
|
300
|
+
" muuuuse <seat> flow on continue <seat> [<seat> ...]",
|
|
301
|
+
" Fan that seat's relayed output into one or more additional armed seats.",
|
|
302
|
+
"",
|
|
300
303
|
" muuuuse status",
|
|
304
|
+
" Show all armed seats, flow mode, pair trust, and continuation targets.",
|
|
305
|
+
"",
|
|
306
|
+
" muuuuse stop",
|
|
307
|
+
" Stop every armed seat in the current cwd.",
|
|
301
308
|
"",
|
|
302
309
|
"Flow:",
|
|
303
310
|
" 1. Run `muuuuse 1` in terminal one.",
|
|
@@ -305,7 +312,7 @@ function usage() {
|
|
|
305
312
|
" 3. The odd seat generates the session key and the matching even seat signs it automatically.",
|
|
306
313
|
" 4. Additional pairs work the same way: `3/4`, `5/6`, `7/8`...",
|
|
307
314
|
" 5. Optional: arm each seat with `flow on` or `flow off`.",
|
|
308
|
-
" 6. Optional: add `continue <seat
|
|
315
|
+
" 6. Optional: add `continue <seat> [<seat> ...]` to forward that seat's relayed output into more armed seats.",
|
|
309
316
|
" 7. Use those armed shells normally.",
|
|
310
317
|
" 8. `flow off` sends final answers only. `flow on` keeps assistant commentary bouncing.",
|
|
311
318
|
" 9. Run `muuuuse status` or `muuuuse stop` from any shell.",
|