muuuuse 2.3.5 → 2.3.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "2.3.5",
3
+ "version": "2.3.6",
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, getPartnerSeatId, normalizeSeatId, usage } = require("./util");
1
+ const { BRAND, 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,9 @@ async function main(argv = process.argv.slice(2)) {
56
56
 
57
57
  const seatId = normalizeSeatId(command);
58
58
  if (seatId) {
59
- const { flowMode, continueSeatId, continueTargets } = parseSeatOptions(command, argv.slice(1));
59
+ const { flowMode, continueSeatId } = parseSeatOptions(command, argv.slice(1));
60
60
  const seat = new ArmedSeat({
61
61
  cwd: process.cwd(),
62
- continueTargets,
63
62
  continueSeatId,
64
63
  flowMode,
65
64
  seatId,
@@ -84,9 +83,8 @@ function renderSeatStatus(seat) {
84
83
  if (seat.partnerLive) {
85
84
  bits.push("peer live");
86
85
  }
87
- const renderedLinks = renderLinkTargets(seat);
88
- if (renderedLinks) {
89
- bits.push(`link ${renderedLinks}`);
86
+ if (seat.continueSeatId) {
87
+ bits.push(`continue ${seat.continueSeatId}`);
90
88
  }
91
89
  if (seat.trust) {
92
90
  bits.push(`trust ${seat.trust}`);
@@ -105,31 +103,11 @@ function renderSeatStatus(seat) {
105
103
  return output;
106
104
  }
107
105
 
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
-
120
- return targets
121
- .map((target) => `${target.targetSeatId}:${target.flowMode}`)
122
- .join(", ");
123
- }
124
-
125
106
  function parseSeatOptions(command, args) {
126
- const seatId = normalizeSeatId(command);
127
107
  let flowMode = "off";
128
108
  let continueSeatId = null;
129
- let continueTargets = [];
130
- let index = 0;
131
109
 
132
- while (index < args.length) {
110
+ for (let index = 0; index < args.length;) {
133
111
  const token = String(args[index] || "").trim().toLowerCase();
134
112
 
135
113
  if (token === "flow") {
@@ -143,23 +121,10 @@ function parseSeatOptions(command, args) {
143
121
  }
144
122
 
145
123
  if (token === "continue") {
146
- const parsedTargets = parseContinueTargets(args.slice(index + 1), flowMode);
147
- if (parsedTargets.targets.length > 0) {
148
- continueTargets = mergeTargets(continueTargets, parsedTargets.targets);
149
- continueSeatId = continueTargets[0].targetSeatId;
150
- index += 1 + parsedTargets.consumed;
151
- continue;
152
- }
153
- break;
154
- }
155
-
156
- if (token === "link") {
157
- const parsedLinks = parseLinkTargets(args.slice(index + 1), seatId, flowMode);
158
- if (parsedLinks.consumed > 0) {
159
- flowMode = parsedLinks.flowMode;
160
- continueTargets = mergeTargets(continueTargets, parsedLinks.continueTargets);
161
- continueSeatId = continueTargets[0]?.targetSeatId || null;
162
- index += 1 + parsedLinks.consumed;
124
+ const targetSeatId = normalizeSeatId(args[index + 1]);
125
+ if (targetSeatId) {
126
+ continueSeatId = targetSeatId;
127
+ index += 2;
163
128
  continue;
164
129
  }
165
130
  break;
@@ -168,97 +133,25 @@ function parseSeatOptions(command, args) {
168
133
  break;
169
134
  }
170
135
 
171
- if (index === args.length) {
172
- return { flowMode, continueSeatId, continueTargets };
136
+ if (args.length === 0 || (flowMode || continueSeatId !== null) && consumedAllArgs(args, flowMode, continueSeatId)) {
137
+ return { flowMode, continueSeatId };
173
138
  }
174
139
 
175
140
  throw new Error(
176
- `\`muuuuse ${command}\` accepts no extra arguments, \`flow on\` / \`flow off\`, optional \`continue <seat>\`, or \`link <seat> flow on [<seat> flow off ...]\`. Run it directly in the terminal you want to arm.`
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.`
177
142
  );
178
143
  }
179
144
 
180
- function mergeTargets(existingTargets, nextTargets) {
181
- const merged = [];
182
- for (const target of Array.isArray(existingTargets) ? existingTargets : []) {
183
- upsertTarget(merged, target);
184
- }
185
- for (const target of Array.isArray(nextTargets) ? nextTargets : []) {
186
- upsertTarget(merged, target);
187
- }
188
-
189
- return merged;
190
- }
191
-
192
- function parseContinueTargets(args, defaultFlowMode) {
193
- const targets = [];
194
- let consumed = 0;
195
-
196
- while (consumed < args.length) {
197
- const targetSeatId = normalizeSeatId(args[consumed]);
198
- if (!targetSeatId) {
199
- break;
200
- }
201
-
202
- const nextFlowMode = parseFlowModeToken(args[consumed + 1], args[consumed + 2]);
203
- const target = {
204
- targetSeatId,
205
- flowMode: nextFlowMode || defaultFlowMode,
206
- };
207
- upsertTarget(targets, target);
208
- consumed += nextFlowMode ? 3 : 1;
209
- }
210
-
211
- return { consumed, targets };
212
- }
213
-
214
- function parseLinkTargets(args, seatId, defaultFlowMode) {
215
- const partnerSeatId = seatId ? getPartnerSeatId(seatId) : null;
216
- const continueTargets = [];
217
- let flowMode = defaultFlowMode;
218
- let consumed = 0;
219
-
220
- while (consumed < args.length) {
221
- const targetSeatId = normalizeSeatId(args[consumed]);
222
- if (!targetSeatId) {
223
- break;
224
- }
225
-
226
- const targetFlowMode = parseFlowModeToken(args[consumed + 1], args[consumed + 2]);
227
- if (!targetFlowMode) {
228
- break;
229
- }
230
-
231
- if (targetSeatId === partnerSeatId) {
232
- flowMode = targetFlowMode;
233
- } else {
234
- upsertTarget(continueTargets, {
235
- targetSeatId,
236
- flowMode: targetFlowMode,
237
- });
238
- }
239
-
240
- consumed += 3;
241
- }
242
-
243
- return { consumed, continueTargets, flowMode };
244
- }
245
-
246
- function parseFlowModeToken(flowToken, modeToken) {
247
- const normalizedFlowToken = String(flowToken || "").trim().toLowerCase();
248
- const normalizedModeToken = String(modeToken || "").trim().toLowerCase();
249
- if (normalizedFlowToken === "flow" && (normalizedModeToken === "on" || normalizedModeToken === "off")) {
250
- return normalizedModeToken;
145
+ function consumedAllArgs(args, flowMode, continueSeatId) {
146
+ const expected = [];
147
+ if (flowMode !== "off" || args.includes("flow")) {
148
+ expected.push("flow", flowMode);
251
149
  }
252
- return null;
253
- }
254
-
255
- function upsertTarget(targets, nextTarget) {
256
- const existingIndex = targets.findIndex((entry) => entry.targetSeatId === nextTarget.targetSeatId);
257
- if (existingIndex >= 0) {
258
- targets[existingIndex] = nextTarget;
259
- return;
150
+ if (continueSeatId !== null || args.includes("continue")) {
151
+ expected.push("continue", String(continueSeatId));
260
152
  }
261
- targets.push(nextTarget);
153
+ return expected.length === args.length &&
154
+ expected.every((value, index) => String(args[index]).trim().toLowerCase() === String(value).trim().toLowerCase());
262
155
  }
263
156
 
264
157
  module.exports = {
package/src/runtime.js CHANGED
@@ -91,26 +91,6 @@ function normalizeContinueSeatId(value) {
91
91
  return seatId || null;
92
92
  }
93
93
 
94
- function normalizeContinueTargets(targets, defaultFlowMode = "off") {
95
- const normalized = [];
96
- const seen = new Set();
97
-
98
- for (const entry of Array.isArray(targets) ? targets : []) {
99
- const targetSeatId = normalizeSeatId(entry?.targetSeatId ?? entry?.seatId ?? entry);
100
- if (!targetSeatId || seen.has(targetSeatId)) {
101
- continue;
102
- }
103
-
104
- seen.add(targetSeatId);
105
- normalized.push({
106
- targetSeatId,
107
- flowMode: normalizeFlowMode(entry?.flowMode ?? entry?.flow ?? defaultFlowMode),
108
- });
109
- }
110
-
111
- return normalized;
112
- }
113
-
114
94
  function resolveShell() {
115
95
  const shell = String(process.env.SHELL || "").trim();
116
96
  return shell || "/bin/bash";
@@ -491,7 +471,6 @@ function buildAnswerSignaturePayload(sessionName, challenge, entry) {
491
471
  seatId: entry.seatId,
492
472
  origin: entry.origin,
493
473
  phase: entry.phase || "final_answer",
494
- flowMode: entry.flowMode || "off",
495
474
  createdAt: entry.createdAt,
496
475
  text: entry.text,
497
476
  });
@@ -511,7 +490,6 @@ function buildContinuationEntry(sourceSessionName, targetSeatId, entry) {
511
490
  chainId: entry.chainId,
512
491
  hop: entry.hop,
513
492
  sourceAnswerId: entry.id,
514
- flowMode: entry.flowMode || null,
515
493
  publicKey: entry.publicKey || null,
516
494
  signature: entry.signature || null,
517
495
  };
@@ -621,7 +599,7 @@ async function sendTextAndEnter(child, text, shouldAbort = () => false) {
621
599
  }
622
600
 
623
601
  try {
624
- child.write("\r");
602
+ child.write("\n");
625
603
  } catch {
626
604
  return false;
627
605
  }
@@ -635,15 +613,9 @@ class ArmedSeat {
635
613
  this.partnerSeatId = getPartnerSeatId(options.seatId);
636
614
  this.anchorSeatId = isAnchorSeat(options.seatId) ? options.seatId : this.partnerSeatId;
637
615
  this.flowMode = normalizeFlowMode(options.flowMode);
638
- this.continueTargets = normalizeContinueTargets(
639
- options.continueTargets || (
640
- options.continueSeatId ? [{ targetSeatId: options.continueSeatId, flowMode: options.flowMode }] : []
641
- ),
642
- this.flowMode
643
- );
644
- this.continueSeatId = this.continueTargets[0]?.targetSeatId || normalizeContinueSeatId(options.continueSeatId);
616
+ this.continueSeatId = normalizeContinueSeatId(options.continueSeatId);
645
617
  this.cwd = normalizeWorkingPath(options.cwd);
646
- if (this.continueTargets.some((target) => target.targetSeatId === this.seatId)) {
618
+ if (this.continueSeatId === this.seatId) {
647
619
  throw new Error(`\`muuuuse ${this.seatId}\` cannot continue to itself.`);
648
620
  }
649
621
  this.sessionName = resolveSessionName(this.cwd, this.seatId);
@@ -720,7 +692,6 @@ class ArmedSeat {
720
692
  sessionName: this.sessionName,
721
693
  flowMode: this.flowMode,
722
694
  continueSeatId: this.continueSeatId,
723
- continueTargets: this.continueTargets,
724
695
  cwd: this.cwd,
725
696
  pid: process.pid,
726
697
  childPid: this.childPid,
@@ -737,7 +708,6 @@ class ArmedSeat {
737
708
  sessionName: this.sessionName,
738
709
  flowMode: this.flowMode,
739
710
  continueSeatId: this.continueSeatId,
740
- continueTargets: this.continueTargets,
741
711
  cwd: this.cwd,
742
712
  pid: process.pid,
743
713
  childPid: this.childPid,
@@ -1083,23 +1053,18 @@ class ArmedSeat {
1083
1053
  return Number.isFinite(requestedAtMs) && requestedAtMs > this.startedAtMs;
1084
1054
  }
1085
1055
 
1086
- shouldCaptureCommentary() {
1087
- return this.flowMode === "on" || this.continueTargets.some((target) => target.flowMode === "on");
1088
- }
1089
-
1090
- findContinuationTarget(targetSeatId) {
1091
- const normalizedTargetSeatId = normalizeSeatId(targetSeatId);
1092
- if (!normalizedTargetSeatId) {
1056
+ findContinuationTarget() {
1057
+ if (!this.continueSeatId) {
1093
1058
  return null;
1094
1059
  }
1095
1060
 
1096
1061
  const candidates = listSessionNames()
1097
1062
  .map((sessionName) => {
1098
- if (!getSeatDirIfExists(sessionName, normalizedTargetSeatId)) {
1063
+ if (!getSeatDirIfExists(sessionName, this.continueSeatId)) {
1099
1064
  return null;
1100
1065
  }
1101
1066
 
1102
- const seat = buildSeatReport(sessionName, normalizedTargetSeatId);
1067
+ const seat = buildSeatReport(sessionName, this.continueSeatId);
1103
1068
  if (!seat || !matchesWorkingPath(seat.cwd, this.cwd)) {
1104
1069
  return null;
1105
1070
  }
@@ -1140,8 +1105,7 @@ class ArmedSeat {
1140
1105
  return;
1141
1106
  }
1142
1107
 
1143
- const inboundFlowMode = normalizeFlowMode(entry.flowMode || this.flowMode);
1144
- if (!shouldAcceptInboundEntry(inboundFlowMode, entry)) {
1108
+ if (!shouldAcceptInboundEntry(this.flowMode, entry)) {
1145
1109
  continue;
1146
1110
  }
1147
1111
 
@@ -1153,7 +1117,6 @@ class ArmedSeat {
1153
1117
  seatId: entry.seatId,
1154
1118
  origin: entry.origin || "unknown",
1155
1119
  phase: getRelayPhase(entry),
1156
- flowMode: inboundFlowMode,
1157
1120
  createdAt: entry.createdAt,
1158
1121
  text: payload,
1159
1122
  });
@@ -1209,8 +1172,7 @@ class ArmedSeat {
1209
1172
  return;
1210
1173
  }
1211
1174
 
1212
- const continueFlowMode = normalizeFlowMode(entry.flowMode || this.flowMode);
1213
- if (!shouldAcceptInboundEntry(continueFlowMode, entry)) {
1175
+ if (!shouldAcceptInboundEntry(this.flowMode, entry)) {
1214
1176
  continue;
1215
1177
  }
1216
1178
 
@@ -1422,13 +1384,12 @@ class ArmedSeat {
1422
1384
  }
1423
1385
 
1424
1386
  const answers = [];
1425
- const captureCommentary = this.shouldCaptureCommentary();
1426
1387
  if (detectedAgent.type === "codex") {
1427
1388
  const result = readCodexAnswers(
1428
1389
  this.liveState.sessionFile,
1429
1390
  this.liveState.offset,
1430
1391
  this.liveState.captureSinceMs,
1431
- { flowMode: captureCommentary }
1392
+ { flowMode: this.flowMode === "on" }
1432
1393
  );
1433
1394
  this.liveState.offset = result.nextOffset;
1434
1395
  answers.push(...result.answers);
@@ -1437,7 +1398,7 @@ class ArmedSeat {
1437
1398
  this.liveState.sessionFile,
1438
1399
  this.liveState.offset,
1439
1400
  this.liveState.captureSinceMs,
1440
- { flowMode: captureCommentary }
1401
+ { flowMode: this.flowMode === "on" }
1441
1402
  );
1442
1403
  this.liveState.offset = result.nextOffset;
1443
1404
  answers.push(...result.answers);
@@ -1446,7 +1407,7 @@ class ArmedSeat {
1446
1407
  this.liveState.sessionFile,
1447
1408
  this.liveState.lastMessageId,
1448
1409
  this.liveState.captureSinceMs,
1449
- { flowMode: captureCommentary }
1410
+ { flowMode: this.flowMode === "on" }
1450
1411
  );
1451
1412
  this.liveState.lastMessageId = result.lastMessageId;
1452
1413
  this.liveState.offset = result.fileSize;
@@ -1504,7 +1465,6 @@ class ArmedSeat {
1504
1465
  seatId: this.seatId,
1505
1466
  origin: entry.origin || "unknown",
1506
1467
  phase: entry.phase || "final_answer",
1507
- flowMode: this.flowMode,
1508
1468
  text: payload,
1509
1469
  createdAt: entry.createdAt || new Date().toISOString(),
1510
1470
  chainId: pendingInboundContext?.chainId || entry.chainId || entryId,
@@ -1524,24 +1484,19 @@ class ArmedSeat {
1524
1484
  }
1525
1485
 
1526
1486
  forwardContinuation(signedEntry) {
1527
- if (this.continueTargets.length === 0) {
1487
+ if (!this.continueSeatId) {
1528
1488
  return;
1529
1489
  }
1530
1490
 
1531
- for (const targetEntry of this.continueTargets) {
1532
- const target = this.findContinuationTarget(targetEntry.targetSeatId);
1533
- if (!target) {
1534
- this.log(`[${this.seatId}] continue ${targetEntry.targetSeatId} unavailable`);
1535
- continue;
1536
- }
1537
-
1538
- const continuationEntry = buildContinuationEntry(this.sessionName, target.seatId, {
1539
- ...signedEntry,
1540
- flowMode: targetEntry.flowMode,
1541
- });
1542
- appendJsonl(target.paths.continuePath, continuationEntry);
1543
- this.log(`[${this.seatId} => ${target.seatId} ${targetEntry.flowMode}] ${previewText(continuationEntry.text)}`);
1491
+ const target = this.findContinuationTarget();
1492
+ if (!target) {
1493
+ this.log(`[${this.seatId}] continue ${this.continueSeatId} unavailable`);
1494
+ return;
1544
1495
  }
1496
+
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)}`);
1545
1500
  }
1546
1501
 
1547
1502
  async tick() {
@@ -1590,10 +1545,8 @@ class ArmedSeat {
1590
1545
  this.log(`${BRAND} seat ${this.seatId} armed for ${this.sessionName}.`);
1591
1546
  this.log("Use this shell normally. Codex, Claude, and Gemini relay automatically from their local session logs.");
1592
1547
  this.log(`Seat ${this.seatId} relay mode is flow ${this.flowMode}.`);
1593
- if (this.continueTargets.length > 0) {
1594
- this.log(
1595
- `Seat ${this.seatId} continues to ${this.continueTargets.map((target) => `seat ${target.targetSeatId} (${target.flowMode})`).join(", ")}.`
1596
- );
1548
+ if (this.continueSeatId) {
1549
+ this.log(`Seat ${this.seatId} continues to seat ${this.continueSeatId}.`);
1597
1550
  }
1598
1551
  if (isAnchorSeat(this.seatId)) {
1599
1552
  this.log(`Seat ${this.seatId} generated the session key and is waiting for seat ${this.partnerSeatId} to sign it.`);
@@ -1694,18 +1647,9 @@ function buildSeatReport(sessionName, seatId) {
1694
1647
 
1695
1648
  return {
1696
1649
  seatId,
1697
- partnerSeatId: status?.partnerSeatId || meta?.partnerSeatId || getPartnerSeatId(seatId),
1698
1650
  state: wrapperLive ? status?.state || "running" : "orphaned_child",
1699
1651
  flowMode: status?.flowMode || meta?.flowMode || "off",
1700
1652
  continueSeatId: status?.continueSeatId || meta?.continueSeatId || null,
1701
- continueTargets: normalizeContinueTargets(
1702
- status?.continueTargets || meta?.continueTargets || (
1703
- (status?.continueSeatId || meta?.continueSeatId)
1704
- ? [{ targetSeatId: status?.continueSeatId || meta?.continueSeatId, flowMode: status?.flowMode || meta?.flowMode || "off" }]
1705
- : []
1706
- ),
1707
- status?.flowMode || meta?.flowMode || "off"
1708
- ),
1709
1653
  wrapperPid,
1710
1654
  childPid,
1711
1655
  wrapperLive,
package/src/util.js CHANGED
@@ -285,15 +285,17 @@ function usage() {
285
285
  `${BRAND} arms regular terminals in isolated odd/even pairs and relays assistant output between each pair.`,
286
286
  "",
287
287
  "Usage:",
288
- " muuuuse",
289
288
  " muuuuse 1",
290
- " muuuuse 1 link 2 flow on",
291
- " muuuuse 1 link 2 flow on 3 flow off",
289
+ " muuuuse 1 flow on",
290
+ " muuuuse 1 flow off",
291
+ " muuuuse 1 flow on continue 3",
292
292
  " muuuuse 2",
293
- " muuuuse 2 link 1 flow on",
293
+ " muuuuse 2 flow on",
294
+ " muuuuse 2 flow off",
295
+ " muuuuse 2 flow on continue 3",
294
296
  " muuuuse 3",
295
297
  " muuuuse 4",
296
- " muuuuse 4 link 3 flow off 1 flow on",
298
+ " muuuuse 4 flow on continue 1",
297
299
  " muuuuse stop",
298
300
  " muuuuse status",
299
301
  "",
@@ -302,9 +304,9 @@ function usage() {
302
304
  " 2. Run `muuuuse 2` in terminal two.",
303
305
  " 3. The odd seat generates the session key and the matching even seat signs it automatically.",
304
306
  " 4. Additional pairs work the same way: `3/4`, `5/6`, `7/8`...",
305
- " 5. Use `link <seat> flow on [<seat> flow off ...]` to set each outbound path.",
306
- " 6. Linking the odd/even partner sets the normal pair flow. Extra linked seats become continuations.",
307
- " 7. Legacy `flow on` / `flow off` and `continue` still parse, but `link` is the main command shape.",
307
+ " 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.",
309
+ " 7. Use those armed shells normally.",
308
310
  " 8. `flow off` sends final answers only. `flow on` keeps assistant commentary bouncing.",
309
311
  " 9. Run `muuuuse status` or `muuuuse stop` from any shell.",
310
312
  "",