muuuuse 2.3.4 → 3.1.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/README.md CHANGED
@@ -15,16 +15,13 @@ It does one job:
15
15
  The whole surface is:
16
16
 
17
17
  ```bash
18
- muuuuse
19
18
  muuuuse 1
20
19
  muuuuse 1 flow on
21
20
  muuuuse 1 flow off
22
21
  muuuuse 1 flow off continue 5
23
- muuuuse 1 flow on continue 3 5 6
24
22
  muuuuse 2
25
23
  muuuuse 2 flow off
26
24
  muuuuse 2 flow on continue 3
27
- muuuuse 2 flow on continue 3 5 6
28
25
  muuuuse 3
29
26
  muuuuse 3 flow on
30
27
  muuuuse 4
@@ -51,7 +48,7 @@ Now both shells are armed. `muuuuse 1` generates the session key, `muuuuse 2` si
51
48
 
52
49
  `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.
53
50
 
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.
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.
55
52
 
56
53
  If you want Codex in one and Gemini in the other, start them inside the armed shells:
57
54
 
@@ -81,7 +78,7 @@ muuuuse stop
81
78
 
82
79
  - state lives under `~/.muuuuse`
83
80
  - only the signed armed pair can exchange relay events
84
- - `continue <seat> [<seat> ...]` is a separate local forwarding lane and can target any armed seat numbers
81
+ - `continue <seat>` is a separate local forwarding lane and can target any armed seat number
85
82
  - supported relay detection is built for Codex, Claude, and Gemini
86
83
 
87
84
  ## Install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "2.3.4",
3
+ "version": "3.1.1",
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
@@ -1,4 +1,4 @@
1
- const { BRAND, normalizeSeatId, usage } = require("./util");
1
+ const { BRAND, getPartnerSeatId, normalizeSeatId, usage } = require("./util");
2
2
  const { ArmedSeat, getStatusReport, stopAllSessions } = require("./runtime");
3
3
 
4
4
  async function main(argv = process.argv.slice(2)) {
@@ -56,10 +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, continueSeatIds } = parseSeatOptions(command, argv.slice(1));
59
+ const { flowMode, continueSeatId, continueTargets } = parseSeatOptions(command, argv.slice(1));
60
60
  const seat = new ArmedSeat({
61
61
  cwd: process.cwd(),
62
- continueSeatIds,
62
+ continueTargets,
63
63
  continueSeatId,
64
64
  flowMode,
65
65
  seatId,
@@ -84,11 +84,9 @@ function renderSeatStatus(seat) {
84
84
  if (seat.partnerLive) {
85
85
  bits.push("peer live");
86
86
  }
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(",")}`);
87
+ const renderedLinks = renderLinkTargets(seat);
88
+ if (renderedLinks) {
89
+ bits.push(`link ${renderedLinks}`);
92
90
  }
93
91
  if (seat.trust) {
94
92
  bits.push(`trust ${seat.trust}`);
@@ -107,63 +105,120 @@ function renderSeatStatus(seat) {
107
105
  return output;
108
106
  }
109
107
 
108
+ function renderLinkTargets(seat) {
109
+ const targets = [];
110
+ if (seat.partnerSeatId) {
111
+ targets.push({
112
+ targetSeatId: seat.partnerSeatId,
113
+ flowMode: seat.flowMode || "off",
114
+ });
115
+ }
116
+ for (const target of Array.isArray(seat.continueTargets) ? seat.continueTargets : []) {
117
+ targets.push(target);
118
+ }
119
+ if (targets.length === 0) {
120
+ return "";
121
+ }
122
+ return targets.map((target) => `${target.targetSeatId}:${target.flowMode}`).join(", ");
123
+ }
124
+
110
125
  function parseSeatOptions(command, args) {
126
+ const seatId = normalizeSeatId(command);
111
127
  let flowMode = "off";
112
- const continueSeatIds = [];
113
- let sawContinue = false;
114
- let index = 0;
115
-
116
- while (index < args.length) {
117
- const token = String(args[index] || "").trim().toLowerCase();
118
-
119
- if (token === "flow" && !sawContinue) {
120
- const flowToken = String(args[index + 1] || "").trim().toLowerCase();
121
- if (flowToken === "on" || flowToken === "off") {
122
- flowMode = flowToken;
123
- index += 2;
124
- continue;
125
- }
126
- break;
128
+ let continueSeatId = null;
129
+ let continueTargets = [];
130
+ if (args.length === 0) {
131
+ return { flowMode, continueSeatId, continueTargets };
132
+ }
133
+
134
+ if (String(args[0] || "").trim().toLowerCase() === "link") {
135
+ const parsedLinks = parseLinkTargets(args.slice(1), seatId, flowMode);
136
+ if (parsedLinks.consumed === args.length - 1 && parsedLinks.consumed > 0) {
137
+ return {
138
+ flowMode: parsedLinks.flowMode,
139
+ continueSeatId: parsedLinks.continueTargets[0]?.targetSeatId || null,
140
+ continueTargets: parsedLinks.continueTargets,
141
+ };
142
+ }
143
+ } else {
144
+ let index = 0;
145
+
146
+ const flowToken = String(args[index] || "").trim().toLowerCase();
147
+ const flowModeToken = String(args[index + 1] || "").trim().toLowerCase();
148
+ if (flowToken === "flow" && (flowModeToken === "on" || flowModeToken === "off")) {
149
+ flowMode = flowModeToken;
150
+ index += 2;
127
151
  }
128
152
 
129
- if (token === "continue") {
130
- sawContinue = true;
131
- let consumedTargets = 0;
132
- index += 1;
153
+ const continueToken = String(args[index] || "").trim().toLowerCase();
154
+ const targetSeatId = normalizeSeatId(args[index + 1]);
155
+ if (continueToken === "continue" && targetSeatId) {
156
+ continueSeatId = targetSeatId;
157
+ continueTargets = [{ targetSeatId, flowMode }];
158
+ index += 2;
159
+ }
133
160
 
134
- while (index < args.length) {
135
- const targetSeatId = normalizeSeatId(args[index]);
136
- if (!targetSeatId) {
137
- break;
138
- }
161
+ if (index === args.length) {
162
+ return { flowMode, continueSeatId, continueTargets };
163
+ }
164
+ }
139
165
 
140
- continueSeatIds.push(targetSeatId);
141
- consumedTargets += 1;
142
- index += 1;
143
- }
166
+ throw new Error(
167
+ `\`muuuuse ${command}\` accepts no extra arguments or \`link <seat> flow on [<seat> flow off ...]\`. Run it directly in the terminal you want to arm.`
168
+ );
169
+ }
144
170
 
145
- if (consumedTargets > 0) {
146
- continue;
147
- }
171
+ function parseLinkTargets(args, seatId, defaultFlowMode) {
172
+ const partnerSeatId = seatId ? getPartnerSeatId(seatId) : null;
173
+ const continueTargets = [];
174
+ let flowMode = defaultFlowMode;
175
+ let consumed = 0;
176
+
177
+ while (consumed < args.length) {
178
+ const targetSeatId = normalizeSeatId(args[consumed]);
179
+ if (!targetSeatId) {
148
180
  break;
149
181
  }
150
182
 
151
- break;
183
+ const targetFlowMode = parseFlowModeToken(args[consumed + 1], args[consumed + 2]);
184
+ if (!targetFlowMode) {
185
+ break;
186
+ }
187
+
188
+ if (targetSeatId === partnerSeatId) {
189
+ flowMode = targetFlowMode;
190
+ } else {
191
+ upsertTarget(continueTargets, {
192
+ targetSeatId,
193
+ flowMode: targetFlowMode,
194
+ });
195
+ }
196
+
197
+ consumed += 3;
152
198
  }
153
199
 
154
- if (index === args.length) {
155
- return {
156
- flowMode,
157
- continueSeatId: continueSeatIds[0] || null,
158
- continueSeatIds,
159
- };
200
+ return { consumed, continueTargets, flowMode };
201
+ }
202
+
203
+ function parseFlowModeToken(flowToken, modeToken) {
204
+ const normalizedFlowToken = String(flowToken || "").trim().toLowerCase();
205
+ const normalizedModeToken = String(modeToken || "").trim().toLowerCase();
206
+ if (normalizedFlowToken === "flow" && (normalizedModeToken === "on" || normalizedModeToken === "off")) {
207
+ return normalizedModeToken;
160
208
  }
209
+ return null;
210
+ }
161
211
 
162
- throw new Error(
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.`
164
- );
212
+ function upsertTarget(targets, nextTarget) {
213
+ const existingIndex = targets.findIndex((entry) => entry.targetSeatId === nextTarget.targetSeatId);
214
+ if (existingIndex >= 0) {
215
+ targets[existingIndex] = nextTarget;
216
+ return;
217
+ }
218
+ targets.push(nextTarget);
165
219
  }
166
220
 
167
221
  module.exports = {
168
222
  main,
223
+ parseSeatOptions,
169
224
  };
package/src/runtime.js CHANGED
@@ -42,6 +42,7 @@ const {
42
42
 
43
43
  const TYPE_CHUNK_DELAY_MS = 18;
44
44
  const TYPE_CHUNK_SIZE = 24;
45
+ const TYPE_SUBMIT_DELAY_MS = 60;
45
46
  const MIRROR_SUPPRESSION_WINDOW_MS = 30 * 1000;
46
47
  const PENDING_RELAY_CONTEXT_TTL_MS = 2 * 60 * 1000;
47
48
  const EMITTED_ANSWER_TTL_MS = 5 * 60 * 1000;
@@ -91,34 +92,24 @@ function normalizeContinueSeatId(value) {
91
92
  return seatId || null;
92
93
  }
93
94
 
94
- function normalizeContinueSeatIds(value) {
95
- const values = Array.isArray(value) ? value : [value];
95
+ function normalizeContinueTargets(targets, defaultFlowMode = "off") {
96
+ const normalized = [];
96
97
  const seen = new Set();
97
- const seatIds = [];
98
98
 
99
- for (const entry of values) {
100
- const seatId = normalizeContinueSeatId(entry);
101
- if (!seatId || seen.has(seatId)) {
99
+ for (const entry of Array.isArray(targets) ? targets : []) {
100
+ const targetSeatId = normalizeSeatId(entry?.targetSeatId ?? entry?.seatId ?? entry);
101
+ if (!targetSeatId || seen.has(targetSeatId)) {
102
102
  continue;
103
103
  }
104
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;
105
+ seen.add(targetSeatId);
106
+ normalized.push({
107
+ targetSeatId,
108
+ flowMode: normalizeFlowMode(entry?.flowMode ?? entry?.flow ?? defaultFlowMode),
109
+ });
119
110
  }
120
111
 
121
- return continueSeatId;
112
+ return normalized;
122
113
  }
123
114
 
124
115
  function resolveShell() {
@@ -501,6 +492,7 @@ function buildAnswerSignaturePayload(sessionName, challenge, entry) {
501
492
  seatId: entry.seatId,
502
493
  origin: entry.origin,
503
494
  phase: entry.phase || "final_answer",
495
+ flowMode: entry.flowMode || "off",
504
496
  createdAt: entry.createdAt,
505
497
  text: entry.text,
506
498
  });
@@ -520,6 +512,7 @@ function buildContinuationEntry(sourceSessionName, targetSeatId, entry) {
520
512
  chainId: entry.chainId,
521
513
  hop: entry.hop,
522
514
  sourceAnswerId: entry.id,
515
+ flowMode: entry.flowMode || null,
523
516
  publicKey: entry.publicKey || null,
524
517
  signature: entry.signature || null,
525
518
  };
@@ -628,6 +621,14 @@ async function sendTextAndEnter(child, text, shouldAbort = () => false) {
628
621
  return false;
629
622
  }
630
623
 
624
+ // Some TUIs treat fast, chunked relay typing as paste input and suppress an
625
+ // immediate Enter. A short settle delay keeps submit behavior reliable.
626
+ await sleep(TYPE_SUBMIT_DELAY_MS);
627
+
628
+ if (shouldAbort() || !child) {
629
+ return false;
630
+ }
631
+
631
632
  try {
632
633
  child.write("\r");
633
634
  } catch {
@@ -643,12 +644,15 @@ class ArmedSeat {
643
644
  this.partnerSeatId = getPartnerSeatId(options.seatId);
644
645
  this.anchorSeatId = isAnchorSeat(options.seatId) ? options.seatId : this.partnerSeatId;
645
646
  this.flowMode = normalizeFlowMode(options.flowMode);
646
- this.continueSeatIds = normalizeContinueSeatIds(
647
- resolveContinueSeatConfig(options.continueSeatIds, options.continueSeatId)
647
+ this.continueTargets = normalizeContinueTargets(
648
+ options.continueTargets || (
649
+ options.continueSeatId ? [{ targetSeatId: options.continueSeatId, flowMode: options.flowMode }] : []
650
+ ),
651
+ this.flowMode
648
652
  );
649
- this.continueSeatId = this.continueSeatIds[0] || null;
653
+ this.continueSeatId = this.continueTargets[0]?.targetSeatId || normalizeContinueSeatId(options.continueSeatId);
650
654
  this.cwd = normalizeWorkingPath(options.cwd);
651
- if (this.continueSeatIds.includes(this.seatId)) {
655
+ if (this.continueTargets.some((target) => target.targetSeatId === this.seatId)) {
652
656
  throw new Error(`\`muuuuse ${this.seatId}\` cannot continue to itself.`);
653
657
  }
654
658
  this.sessionName = resolveSessionName(this.cwd, this.seatId);
@@ -724,8 +728,8 @@ class ArmedSeat {
724
728
  partnerSeatId: this.partnerSeatId,
725
729
  sessionName: this.sessionName,
726
730
  flowMode: this.flowMode,
727
- continueSeatIds: this.continueSeatIds,
728
731
  continueSeatId: this.continueSeatId,
732
+ continueTargets: this.continueTargets,
729
733
  cwd: this.cwd,
730
734
  pid: process.pid,
731
735
  childPid: this.childPid,
@@ -741,8 +745,8 @@ class ArmedSeat {
741
745
  partnerSeatId: this.partnerSeatId,
742
746
  sessionName: this.sessionName,
743
747
  flowMode: this.flowMode,
744
- continueSeatIds: this.continueSeatIds,
745
748
  continueSeatId: this.continueSeatId,
749
+ continueTargets: this.continueTargets,
746
750
  cwd: this.cwd,
747
751
  pid: process.pid,
748
752
  childPid: this.childPid,
@@ -1088,8 +1092,12 @@ class ArmedSeat {
1088
1092
  return Number.isFinite(requestedAtMs) && requestedAtMs > this.startedAtMs;
1089
1093
  }
1090
1094
 
1095
+ shouldCaptureCommentary() {
1096
+ return this.flowMode === "on" || this.continueTargets.some((target) => target.flowMode === "on");
1097
+ }
1098
+
1091
1099
  findContinuationTarget(targetSeatId) {
1092
- const normalizedTargetSeatId = normalizeContinueSeatId(targetSeatId);
1100
+ const normalizedTargetSeatId = normalizeSeatId(targetSeatId);
1093
1101
  if (!normalizedTargetSeatId) {
1094
1102
  return null;
1095
1103
  }
@@ -1141,7 +1149,8 @@ class ArmedSeat {
1141
1149
  return;
1142
1150
  }
1143
1151
 
1144
- if (!shouldAcceptInboundEntry(this.flowMode, entry)) {
1152
+ const inboundFlowMode = normalizeFlowMode(entry.flowMode || this.flowMode);
1153
+ if (!shouldAcceptInboundEntry(inboundFlowMode, entry)) {
1145
1154
  continue;
1146
1155
  }
1147
1156
 
@@ -1153,6 +1162,7 @@ class ArmedSeat {
1153
1162
  seatId: entry.seatId,
1154
1163
  origin: entry.origin || "unknown",
1155
1164
  phase: getRelayPhase(entry),
1165
+ flowMode: inboundFlowMode,
1156
1166
  createdAt: entry.createdAt,
1157
1167
  text: payload,
1158
1168
  });
@@ -1208,7 +1218,8 @@ class ArmedSeat {
1208
1218
  return;
1209
1219
  }
1210
1220
 
1211
- if (!shouldAcceptInboundEntry(this.flowMode, entry)) {
1221
+ const continueFlowMode = normalizeFlowMode(entry.flowMode || this.flowMode);
1222
+ if (!shouldAcceptInboundEntry(continueFlowMode, entry)) {
1212
1223
  continue;
1213
1224
  }
1214
1225
 
@@ -1420,12 +1431,13 @@ class ArmedSeat {
1420
1431
  }
1421
1432
 
1422
1433
  const answers = [];
1434
+ const captureCommentary = this.shouldCaptureCommentary();
1423
1435
  if (detectedAgent.type === "codex") {
1424
1436
  const result = readCodexAnswers(
1425
1437
  this.liveState.sessionFile,
1426
1438
  this.liveState.offset,
1427
1439
  this.liveState.captureSinceMs,
1428
- { flowMode: this.flowMode === "on" }
1440
+ { flowMode: captureCommentary }
1429
1441
  );
1430
1442
  this.liveState.offset = result.nextOffset;
1431
1443
  answers.push(...result.answers);
@@ -1434,7 +1446,7 @@ class ArmedSeat {
1434
1446
  this.liveState.sessionFile,
1435
1447
  this.liveState.offset,
1436
1448
  this.liveState.captureSinceMs,
1437
- { flowMode: this.flowMode === "on" }
1449
+ { flowMode: captureCommentary }
1438
1450
  );
1439
1451
  this.liveState.offset = result.nextOffset;
1440
1452
  answers.push(...result.answers);
@@ -1443,7 +1455,7 @@ class ArmedSeat {
1443
1455
  this.liveState.sessionFile,
1444
1456
  this.liveState.lastMessageId,
1445
1457
  this.liveState.captureSinceMs,
1446
- { flowMode: this.flowMode === "on" }
1458
+ { flowMode: captureCommentary }
1447
1459
  );
1448
1460
  this.liveState.lastMessageId = result.lastMessageId;
1449
1461
  this.liveState.offset = result.fileSize;
@@ -1501,6 +1513,7 @@ class ArmedSeat {
1501
1513
  seatId: this.seatId,
1502
1514
  origin: entry.origin || "unknown",
1503
1515
  phase: entry.phase || "final_answer",
1516
+ flowMode: this.flowMode,
1504
1517
  text: payload,
1505
1518
  createdAt: entry.createdAt || new Date().toISOString(),
1506
1519
  chainId: pendingInboundContext?.chainId || entry.chainId || entryId,
@@ -1520,20 +1533,23 @@ class ArmedSeat {
1520
1533
  }
1521
1534
 
1522
1535
  forwardContinuation(signedEntry) {
1523
- if (this.continueSeatIds.length === 0) {
1536
+ if (this.continueTargets.length === 0) {
1524
1537
  return;
1525
1538
  }
1526
1539
 
1527
- for (const continueSeatId of this.continueSeatIds) {
1528
- const target = this.findContinuationTarget(continueSeatId);
1540
+ for (const targetEntry of this.continueTargets) {
1541
+ const target = this.findContinuationTarget(targetEntry.targetSeatId);
1529
1542
  if (!target) {
1530
- this.log(`[${this.seatId}] continue ${continueSeatId} unavailable`);
1543
+ this.log(`[${this.seatId}] continue ${targetEntry.targetSeatId} unavailable`);
1531
1544
  continue;
1532
1545
  }
1533
1546
 
1534
- const continuationEntry = buildContinuationEntry(this.sessionName, target.seatId, signedEntry);
1547
+ const continuationEntry = buildContinuationEntry(this.sessionName, target.seatId, {
1548
+ ...signedEntry,
1549
+ flowMode: targetEntry.flowMode,
1550
+ });
1535
1551
  appendJsonl(target.paths.continuePath, continuationEntry);
1536
- this.log(`[${this.seatId} => ${target.seatId}] ${previewText(continuationEntry.text)}`);
1552
+ this.log(`[${this.seatId} => ${target.seatId} ${targetEntry.flowMode}] ${previewText(continuationEntry.text)}`);
1537
1553
  }
1538
1554
  }
1539
1555
 
@@ -1583,9 +1599,10 @@ class ArmedSeat {
1583
1599
  this.log(`${BRAND} seat ${this.seatId} armed for ${this.sessionName}.`);
1584
1600
  this.log("Use this shell normally. Codex, Claude, and Gemini relay automatically from their local session logs.");
1585
1601
  this.log(`Seat ${this.seatId} relay mode is flow ${this.flowMode}.`);
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(", ")}.`);
1602
+ if (this.continueTargets.length > 0) {
1603
+ this.log(
1604
+ `Seat ${this.seatId} continues to ${this.continueTargets.map((target) => `seat ${target.targetSeatId} (${target.flowMode})`).join(", ")}.`
1605
+ );
1589
1606
  }
1590
1607
  if (isAnchorSeat(this.seatId)) {
1591
1608
  this.log(`Seat ${this.seatId} generated the session key and is waiting for seat ${this.partnerSeatId} to sign it.`);
@@ -1684,19 +1701,20 @@ function buildSeatReport(sessionName, seatId) {
1684
1701
  return null;
1685
1702
  }
1686
1703
 
1687
- const continueSeatIds = normalizeContinueSeatIds(
1688
- resolveContinueSeatConfig(
1689
- status?.continueSeatIds ?? meta?.continueSeatIds,
1690
- status?.continueSeatId ?? meta?.continueSeatId
1691
- )
1692
- );
1693
-
1694
1704
  return {
1695
1705
  seatId,
1706
+ partnerSeatId: status?.partnerSeatId || meta?.partnerSeatId || getPartnerSeatId(seatId),
1696
1707
  state: wrapperLive ? status?.state || "running" : "orphaned_child",
1697
1708
  flowMode: status?.flowMode || meta?.flowMode || "off",
1698
- continueSeatId: continueSeatIds[0] || null,
1699
- continueSeatIds,
1709
+ continueSeatId: status?.continueSeatId || meta?.continueSeatId || null,
1710
+ continueTargets: normalizeContinueTargets(
1711
+ status?.continueTargets || meta?.continueTargets || (
1712
+ (status?.continueSeatId || meta?.continueSeatId)
1713
+ ? [{ targetSeatId: status?.continueSeatId || meta?.continueSeatId, flowMode: status?.flowMode || meta?.flowMode || "off" }]
1714
+ : []
1715
+ ),
1716
+ status?.flowMode || meta?.flowMode || "off"
1717
+ ),
1700
1718
  wrapperPid,
1701
1719
  childPid,
1702
1720
  wrapperLive,
package/src/util.js CHANGED
@@ -284,36 +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
- "Command List:",
287
+ "Usage:",
288
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
- "",
303
- " muuuuse status",
304
- " Show all armed seats, flow mode, pair trust, and continuation targets.",
305
- "",
289
+ " muuuuse 1",
290
+ " muuuuse 1 link 2 flow off",
291
+ " muuuuse 1 link 2 flow on",
292
+ " muuuuse 1 link 2 flow on 3 flow off",
293
+ " muuuuse 2",
294
+ " muuuuse 3",
295
+ " muuuuse 4",
296
+ " muuuuse 4 link 3 flow off 1 flow on",
306
297
  " muuuuse stop",
307
- " Stop every armed seat in the current cwd.",
298
+ " muuuuse status",
308
299
  "",
309
300
  "Flow:",
310
- " 1. Run `muuuuse 1` in terminal one.",
311
- " 2. Run `muuuuse 2` in terminal two.",
301
+ " 1. Run `muuuuse 1` in terminal one, then `muuuuse 2` in terminal two.",
302
+ " 2. Bare seats default to final-only pair relay.",
312
303
  " 3. The odd seat generates the session key and the matching even seat signs it automatically.",
313
304
  " 4. Additional pairs work the same way: `3/4`, `5/6`, `7/8`...",
314
- " 5. Optional: arm each seat with `flow on` or `flow off`.",
315
- " 6. Optional: add `continue <seat> [<seat> ...]` to forward that seat's relayed output into more armed seats.",
316
- " 7. Use those armed shells normally.",
305
+ " 5. Use `link <seat> flow on [<seat> flow off ...]` to set each outbound route.",
306
+ " 6. Include the odd/even partner in `link` to set normal pair flow.",
307
+ " 7. Any extra linked seats receive routed copies with their own flow mode.",
317
308
  " 8. `flow off` sends final answers only. `flow on` keeps assistant commentary bouncing.",
318
309
  " 9. Run `muuuuse status` or `muuuuse stop` from any shell.",
319
310
  "",