muuuuse 3.1.1 → 3.3.2

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
@@ -5,11 +5,10 @@
5
5
  It does one job:
6
6
  - arm terminal one with `muuuuse 1`
7
7
  - arm terminal two with `muuuuse 2`
8
- - have seat 1 generate a session key and seat 2 sign it
9
- - additional isolated pairs work the same way: `3/4`, `5/6`, `7/8`, ...
8
+ - let each seat define signed relay links to any other armed seat
10
9
  - choose per-seat relay mode with `flow on` or `flow off`
11
10
  - watch Codex, Claude, or Gemini for local assistant output
12
- - inject that output into the other armed terminal
11
+ - inject that output into linked armed terminals
13
12
  - keep looping until you stop it
14
13
 
15
14
  The whole surface is:
@@ -19,9 +18,11 @@ muuuuse 1
19
18
  muuuuse 1 flow on
20
19
  muuuuse 1 flow off
21
20
  muuuuse 1 flow off continue 5
21
+ muuuuse 1 link 2 flow on 3 flow off 5 flow off
22
22
  muuuuse 2
23
23
  muuuuse 2 flow off
24
24
  muuuuse 2 flow on continue 3
25
+ muuuuse 2 link 1 flow off 3 flow on 4 flow on
25
26
  muuuuse 3
26
27
  muuuuse 3 flow on
27
28
  muuuuse 4
@@ -35,20 +36,20 @@ muuuuse stop
35
36
  Terminal 1:
36
37
 
37
38
  ```bash
38
- muuuuse 1 flow on
39
+ muuuuse 1 link 2 flow on
39
40
  ```
40
41
 
41
42
  Terminal 2:
42
43
 
43
44
  ```bash
44
- muuuuse 2 flow off
45
+ muuuuse 2 link 1 flow off link 3 flow on link 4 flow on
45
46
  ```
46
47
 
47
- Now both shells are armed. `muuuuse 1` generates the session key, `muuuuse 2` signs it, and only that signed pair relays. Every odd/even adjacent pair works the same way in parallel: `3/4`, `5/6`, `7/8`, and so on. Use those shells normally.
48
+ Now both shells are armed in the same cwd and join the same relay graph. Every seat has its own Ed25519 keypair. Each forwarded relay is signed by the sending seat. A target seat only accepts inbound relays when the sender linked to that target, so the graph can be open-ended without becoming an all-to-all broadcast.
48
49
 
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.
50
+ `link <seat> flow on` means that outbound edge sends commentary and final answers. `link <seat> flow off` means that outbound edge sends final answers only. This is sender-side routing, not receiver-side filtering.
50
51
 
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.
52
+ `continue <seat>` is shorthand for one outbound link that uses the seat's default `flow on|off`. Explicit `link ... flow ...` edges are the full model and can be arranged into loops such as `1 -> 2 -> 3 -> 4 -> 1`.
52
53
 
53
54
  If you want Codex in one and Gemini in the other, start them inside the armed shells:
54
55
 
@@ -60,7 +61,7 @@ codex
60
61
  gemini
61
62
  ```
62
63
 
63
- `🔌Muuuuse` tails the local session logs for supported CLIs, relays according to each seat's flow mode, types that output into the other seat, and then sends Enter as a separate keystroke.
64
+ `🔌Muuuuse` tails the local session logs for supported CLIs, relays according to each outbound link's flow mode, types that output into the target seat, and then sends Enter as a separate keystroke.
64
65
 
65
66
  Check the live state from any terminal:
66
67
 
@@ -77,8 +78,9 @@ muuuuse stop
77
78
  ## Notes
78
79
 
79
80
  - state lives under `~/.muuuuse`
80
- - 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
81
+ - all armed seats in the same cwd share one relay session graph
82
+ - only signed relays from senders that linked the target seat are accepted
83
+ - `continue <seat>` is a convenience alias for a single signed outbound link
82
84
  - supported relay detection is built for Codex, Claude, and Gemini
83
85
 
84
86
  ## Install
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "3.1.1",
4
- "description": "🔌Muuuuse arms regular terminals in isolated pairs and can continue relay output into any other armed seat.",
3
+ "version": "3.3.2",
4
+ "description": "🔌Muuuuse arms regular terminals and relays assistant output across signed terminal links.",
5
5
  "type": "commonjs",
6
6
  "bin": {
7
7
  "muuuuse": "bin/muuse.js"
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)) {
@@ -59,8 +59,8 @@ async function main(argv = process.argv.slice(2)) {
59
59
  const { flowMode, continueSeatId, continueTargets } = parseSeatOptions(command, argv.slice(1));
60
60
  const seat = new ArmedSeat({
61
61
  cwd: process.cwd(),
62
- continueTargets,
63
62
  continueSeatId,
63
+ continueTargets,
64
64
  flowMode,
65
65
  seatId,
66
66
  });
@@ -81,15 +81,11 @@ function renderSeatStatus(seat) {
81
81
  `child ${seat.childPid || "-"}`,
82
82
  ];
83
83
 
84
- if (seat.partnerLive) {
85
- bits.push("peer live");
86
- }
87
- const renderedLinks = renderLinkTargets(seat);
88
- if (renderedLinks) {
89
- bits.push(`link ${renderedLinks}`);
84
+ if (seat.continueSeatId) {
85
+ bits.push(`continue ${seat.continueSeatId}`);
90
86
  }
91
- if (seat.trust) {
92
- bits.push(`trust ${seat.trust}`);
87
+ if (Array.isArray(seat.continueTargets) && seat.continueTargets.length > 0) {
88
+ bits.push(`links ${renderLinkTargets(seat.continueTargets)}`);
93
89
  }
94
90
  if (seat.lastAnswerAt) {
95
91
  bits.push(`last answer ${seat.lastAnswerAt}`);
@@ -105,21 +101,10 @@ function renderSeatStatus(seat) {
105
101
  return output;
106
102
  }
107
103
 
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(", ");
104
+ function renderLinkTargets(targets) {
105
+ return targets
106
+ .map((target) => `${target.seatId}:${target.flowMode}`)
107
+ .join(", ");
123
108
  }
124
109
 
125
110
  function parseSeatOptions(command, args) {
@@ -127,98 +112,121 @@ function parseSeatOptions(command, args) {
127
112
  let flowMode = "off";
128
113
  let continueSeatId = null;
129
114
  let continueTargets = [];
130
- if (args.length === 0) {
131
- return { flowMode, continueSeatId, continueTargets };
132
- }
115
+ let index = 0;
133
116
 
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;
117
+ for (; index < args.length;) {
118
+ const token = String(args[index] || "").trim().toLowerCase();
119
+
120
+ if (token === "flow") {
121
+ const nextFlowMode = parseFlowModeToken(args[index + 1]);
122
+ if (nextFlowMode) {
123
+ flowMode = nextFlowMode;
124
+ index += 2;
125
+ continue;
126
+ }
127
+ break;
151
128
  }
152
129
 
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;
130
+ if (token === "continue") {
131
+ const targetSeatId = normalizeSeatId(args[index + 1]);
132
+ if (targetSeatId && targetSeatId !== seatId) {
133
+ continueSeatId = targetSeatId;
134
+ index += 2;
135
+ continue;
136
+ }
137
+ break;
159
138
  }
160
139
 
161
- if (index === args.length) {
162
- return { flowMode, continueSeatId, continueTargets };
140
+ if (token === "link") {
141
+ const parsed = parseLinkTargets(seatId, args, index + 1);
142
+ if (!parsed) {
143
+ break;
144
+ }
145
+
146
+ continueTargets = mergeTargets(continueTargets, parsed.continueTargets);
147
+ index = parsed.nextIndex;
148
+ continue;
163
149
  }
150
+
151
+ break;
152
+ }
153
+
154
+ if (index === args.length) {
155
+ return { flowMode, continueSeatId, continueTargets };
164
156
  }
165
157
 
166
158
  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.`
159
+ `\`muuuuse ${command}\` accepts \`flow on\` / \`flow off\`, optional \`continue <seat>\`, and optional \`link <seat> flow on|off ...\` groups. Run it directly in the terminal you want to arm.`
168
160
  );
169
161
  }
170
162
 
171
- function parseLinkTargets(args, seatId, defaultFlowMode) {
172
- const partnerSeatId = seatId ? getPartnerSeatId(seatId) : null;
163
+ function mergeTargets(currentTargets, nextTargets) {
164
+ const merged = [...currentTargets];
165
+ for (const target of nextTargets) {
166
+ const currentIndex = merged.findIndex((entry) => entry.seatId === target.seatId);
167
+ if (currentIndex !== -1) {
168
+ merged.splice(currentIndex, 1);
169
+ }
170
+ merged.push(target);
171
+ }
172
+ return merged;
173
+ }
174
+
175
+ function parseLinkTargets(seatId, args, startIndex) {
176
+ let index = startIndex;
173
177
  const continueTargets = [];
174
- let flowMode = defaultFlowMode;
175
- let consumed = 0;
176
178
 
177
- while (consumed < args.length) {
178
- const targetSeatId = normalizeSeatId(args[consumed]);
179
- if (!targetSeatId) {
179
+ while (index < args.length) {
180
+ const targetSeatId = normalizeSeatId(args[index]);
181
+ if (!targetSeatId || targetSeatId === seatId) {
180
182
  break;
181
183
  }
182
184
 
183
- const targetFlowMode = parseFlowModeToken(args[consumed + 1], args[consumed + 2]);
184
- if (!targetFlowMode) {
185
+ if (String(args[index + 1] || "").trim().toLowerCase() !== "flow") {
185
186
  break;
186
187
  }
187
188
 
188
- if (targetSeatId === partnerSeatId) {
189
- flowMode = targetFlowMode;
190
- } else {
191
- upsertTarget(continueTargets, {
192
- targetSeatId,
193
- flowMode: targetFlowMode,
194
- });
189
+ const targetFlowMode = parseFlowModeToken(args[index + 2]);
190
+ if (!targetFlowMode) {
191
+ break;
195
192
  }
196
193
 
197
- consumed += 3;
194
+ upsertTarget(continueTargets, {
195
+ seatId: targetSeatId,
196
+ flowMode: targetFlowMode,
197
+ });
198
+
199
+ index += 3;
200
+ }
201
+
202
+ if (index === startIndex) {
203
+ return null;
198
204
  }
199
205
 
200
- return { consumed, continueTargets, flowMode };
206
+ return {
207
+ continueTargets,
208
+ nextIndex: index,
209
+ };
201
210
  }
202
211
 
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;
212
+ function parseFlowModeToken(value) {
213
+ const token = String(value || "").trim().toLowerCase();
214
+ if (token === "on" || token === "off") {
215
+ return token;
208
216
  }
209
217
  return null;
210
218
  }
211
219
 
212
220
  function upsertTarget(targets, nextTarget) {
213
- const existingIndex = targets.findIndex((entry) => entry.targetSeatId === nextTarget.targetSeatId);
214
- if (existingIndex >= 0) {
215
- targets[existingIndex] = nextTarget;
221
+ const currentIndex = targets.findIndex((target) => target.seatId === nextTarget.seatId);
222
+ if (currentIndex === -1) {
223
+ targets.push(nextTarget);
216
224
  return;
217
225
  }
218
- targets.push(nextTarget);
226
+
227
+ targets[currentIndex] = nextTarget;
219
228
  }
220
229
 
221
230
  module.exports = {
222
231
  main,
223
- parseSeatOptions,
224
232
  };