muuuuse 2.3.3 → 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 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>` forwards that seat's relayed output into another armed seat without changing the signed odd/even pair law. This lets you build local loops like `1 -> 2 -> 3 -> 4 -> 1` while every adjacent pair still keeps its own session keypair.
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>` is a separate local forwarding lane and can target any armed seat number
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "2.3.3",
3
+ "version": "2.3.4",
4
4
  "description": "🔌Muuuuse arms regular terminals in isolated pairs and can continue relay output into any other armed seat.",
5
5
  "type": "commonjs",
6
6
  "bin": {
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
- if (seat.continueSeatId) {
87
- bits.push(`continue ${seat.continueSeatId}`);
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
- let continueSeatId = null;
112
+ const continueSeatIds = [];
113
+ let sawContinue = false;
114
+ let index = 0;
109
115
 
110
- for (let index = 0; index < args.length;) {
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
- const targetSeatId = normalizeSeatId(args[index + 1]);
125
- if (targetSeatId) {
126
- continueSeatId = targetSeatId;
127
- index += 2;
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 (args.length === 0 || (flowMode || continueSeatId !== null) && consumedAllArgs(args, flowMode, continueSeatId)) {
137
- return { flowMode, continueSeatId };
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>\`, or both in sequence. Run it directly in the terminal you want to arm.`
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
@@ -91,6 +91,36 @@ function normalizeContinueSeatId(value) {
91
91
  return seatId || null;
92
92
  }
93
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
+
94
124
  function resolveShell() {
95
125
  const shell = String(process.env.SHELL || "").trim();
96
126
  return shell || "/bin/bash";
@@ -613,9 +643,12 @@ class ArmedSeat {
613
643
  this.partnerSeatId = getPartnerSeatId(options.seatId);
614
644
  this.anchorSeatId = isAnchorSeat(options.seatId) ? options.seatId : this.partnerSeatId;
615
645
  this.flowMode = normalizeFlowMode(options.flowMode);
616
- this.continueSeatId = normalizeContinueSeatId(options.continueSeatId);
646
+ this.continueSeatIds = normalizeContinueSeatIds(
647
+ resolveContinueSeatConfig(options.continueSeatIds, options.continueSeatId)
648
+ );
649
+ this.continueSeatId = this.continueSeatIds[0] || null;
617
650
  this.cwd = normalizeWorkingPath(options.cwd);
618
- if (this.continueSeatId === this.seatId) {
651
+ if (this.continueSeatIds.includes(this.seatId)) {
619
652
  throw new Error(`\`muuuuse ${this.seatId}\` cannot continue to itself.`);
620
653
  }
621
654
  this.sessionName = resolveSessionName(this.cwd, this.seatId);
@@ -691,6 +724,7 @@ class ArmedSeat {
691
724
  partnerSeatId: this.partnerSeatId,
692
725
  sessionName: this.sessionName,
693
726
  flowMode: this.flowMode,
727
+ continueSeatIds: this.continueSeatIds,
694
728
  continueSeatId: this.continueSeatId,
695
729
  cwd: this.cwd,
696
730
  pid: process.pid,
@@ -707,6 +741,7 @@ class ArmedSeat {
707
741
  partnerSeatId: this.partnerSeatId,
708
742
  sessionName: this.sessionName,
709
743
  flowMode: this.flowMode,
744
+ continueSeatIds: this.continueSeatIds,
710
745
  continueSeatId: this.continueSeatId,
711
746
  cwd: this.cwd,
712
747
  pid: process.pid,
@@ -1053,18 +1088,19 @@ class ArmedSeat {
1053
1088
  return Number.isFinite(requestedAtMs) && requestedAtMs > this.startedAtMs;
1054
1089
  }
1055
1090
 
1056
- findContinuationTarget() {
1057
- if (!this.continueSeatId) {
1091
+ findContinuationTarget(targetSeatId) {
1092
+ const normalizedTargetSeatId = normalizeContinueSeatId(targetSeatId);
1093
+ if (!normalizedTargetSeatId) {
1058
1094
  return null;
1059
1095
  }
1060
1096
 
1061
1097
  const candidates = listSessionNames()
1062
1098
  .map((sessionName) => {
1063
- if (!getSeatDirIfExists(sessionName, this.continueSeatId)) {
1099
+ if (!getSeatDirIfExists(sessionName, normalizedTargetSeatId)) {
1064
1100
  return null;
1065
1101
  }
1066
1102
 
1067
- const seat = buildSeatReport(sessionName, this.continueSeatId);
1103
+ const seat = buildSeatReport(sessionName, normalizedTargetSeatId);
1068
1104
  if (!seat || !matchesWorkingPath(seat.cwd, this.cwd)) {
1069
1105
  return null;
1070
1106
  }
@@ -1484,19 +1520,21 @@ class ArmedSeat {
1484
1520
  }
1485
1521
 
1486
1522
  forwardContinuation(signedEntry) {
1487
- if (!this.continueSeatId) {
1523
+ if (this.continueSeatIds.length === 0) {
1488
1524
  return;
1489
1525
  }
1490
1526
 
1491
- const target = this.findContinuationTarget();
1492
- if (!target) {
1493
- this.log(`[${this.seatId}] continue ${this.continueSeatId} unavailable`);
1494
- return;
1495
- }
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
+ }
1496
1533
 
1497
- const continuationEntry = buildContinuationEntry(this.sessionName, target.seatId, signedEntry);
1498
- appendJsonl(target.paths.continuePath, continuationEntry);
1499
- this.log(`[${this.seatId} => ${target.seatId}] ${previewText(continuationEntry.text)}`);
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
+ }
1500
1538
  }
1501
1539
 
1502
1540
  async tick() {
@@ -1545,8 +1583,9 @@ class ArmedSeat {
1545
1583
  this.log(`${BRAND} seat ${this.seatId} armed for ${this.sessionName}.`);
1546
1584
  this.log("Use this shell normally. Codex, Claude, and Gemini relay automatically from their local session logs.");
1547
1585
  this.log(`Seat ${this.seatId} relay mode is flow ${this.flowMode}.`);
1548
- if (this.continueSeatId) {
1549
- this.log(`Seat ${this.seatId} continues to seat ${this.continueSeatId}.`);
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(", ")}.`);
1550
1589
  }
1551
1590
  if (isAnchorSeat(this.seatId)) {
1552
1591
  this.log(`Seat ${this.seatId} generated the session key and is waiting for seat ${this.partnerSeatId} to sign it.`);
@@ -1645,11 +1684,19 @@ function buildSeatReport(sessionName, seatId) {
1645
1684
  return null;
1646
1685
  }
1647
1686
 
1687
+ const continueSeatIds = normalizeContinueSeatIds(
1688
+ resolveContinueSeatConfig(
1689
+ status?.continueSeatIds ?? meta?.continueSeatIds,
1690
+ status?.continueSeatId ?? meta?.continueSeatId
1691
+ )
1692
+ );
1693
+
1648
1694
  return {
1649
1695
  seatId,
1650
1696
  state: wrapperLive ? status?.state || "running" : "orphaned_child",
1651
1697
  flowMode: status?.flowMode || meta?.flowMode || "off",
1652
- continueSeatId: status?.continueSeatId || meta?.continueSeatId || null,
1698
+ continueSeatId: continueSeatIds[0] || null,
1699
+ continueSeatIds,
1653
1700
  wrapperPid,
1654
1701
  childPid,
1655
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
- "Usage:",
288
- " muuuuse 1",
289
- " muuuuse 1 flow on",
290
- " muuuuse 1 flow off",
291
- " muuuuse 1 flow on continue 3",
292
- " muuuuse 2",
293
- " muuuuse 2 flow on",
294
- " muuuuse 2 flow off",
295
- " muuuuse 2 flow on continue 3",
296
- " muuuuse 3",
297
- " muuuuse 4",
298
- " muuuuse 4 flow on continue 1",
299
- " muuuuse stop",
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>` to forward that seat's relayed output into another armed 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.",